Adding first and last class to Menu on top level only

I’m using a WordPress Menu to create a navigation structured like this:

  • Home
  • About
  • Contact
    • Sub Page

Using this code from kuroi’s response here I’m able to add first-menu-item and last-menu-item classes to the list items above:

Read More
function add_first_and_last($output) {
    $output = preg_replace('/class="menu-item/', 'class="first-menu-item menu-item', $output, 1);
    $output = substr_replace($output, 'class="last-menu-item menu-item', strripos($output, 'class="menu-item'), strlen('class="menu-item'));
    return $output;
}
add_filter('wp_nav_menu', 'add_first_and_last');

However the last-menu-item class is being added to the Sub Page list item (because it’s the last) rather than to the Contact list item.

Question: How can I make this function apply only to the top level items of a menu?

Thanks!

Related posts

5 comments

  1. I would lean very much towards a custom walker for this but I think I’ve managed to make it work using part of that code and some of my own.

    function add_position_classes_wpse_100781($classes, $item, $args) {
      static $fl;
      if (0 == $item->menu_item_parent) {
        $fl = (empty($fl)) ? 'first' : 'middle';
        $classes[] = $fl.'-menu-item';
      } 
      return $classes;
    }
    add_filter('nav_menu_css_class','add_position_classes_wpse_100781',1,3);
    
    function replace_class_on_last_occurance_wpse_100781($output) {
        $output = substr_replace(
          $output, 
          'last-menu-item ', 
          strripos($output, 'middle-menu-item'), 
          strlen('middle-menu-item')
        );
        return $output;
    }
    add_filter('wp_nav_menu', 'replace_class_on_last_occurance_wpse_100781');
    

    What I did was add first-menu-item and middle-menu-item to top level items only with the first filter on nav_menu_css_class. Then with the second filter I replaced the last occurrence of middle-menu-item with last-menu-item.

    It works for the few test cases I tried.

  2. I have little fix on Bainternet code, because this code not work if last item has sub item

    function wpb_first_and_last_menu_class($items) {
        $items[1]->classes[] = 'first-menu-item'; // add first class
    
        $cnt = count($items);
        while($items[$cnt--]->post_parent != 0); // find last li item
        $items[$cnt+1]->classes[] = 'last-menu-item'; // last item class
        return $items;
    }
    add_filter('wp_nav_menu_objects', 'wpb_first_and_last_menu_class'); //filter to iterate each menu
    
  3. something like this:

    function add_first_and_last_classes_wpa100781($items) {
        $items[1]->classes[] = 'first';
        $items[count($items)]->classes[] = 'last';
        return $items;
    }
    
    add_filter('wp_nav_menu_objects', 'add_first_and_last_classes_wpa100781');
    
  4. I would recommend:

    1) skip adding the first menu item class. That’s extra php that you don’t need. Using ul > li:first-child will let you target the first menu item in either CSS or as a jQuery selector. :first-child has great browser support (more than :last-child, so I would add a class for the last menu item).

    2) Find the last top-level menu item and add a class to it. Drop this code into your theme’s functions.php file and it will add a class to only the last top-level navigation item instead of the very last navigation item like many of the code snippets in the other answers here.

    // Add a class to the last top-level menu item
    function fancy_last_menu_item_class($items) {
        // Create an empty array to hold the array ID's of top level menu items
        $topLevelMenuItemIDs = array();
    
        // Loop through all of the menu $items
        for($i=0,$count=count($items);$i<$count;$i++){
            // Check if the 'menu_item_parent' property is set to '0'
            if($items[$i]->menu_item_parent == 0){   
                // This item has no parent menu items, so it's top-level.
                // Add its array ID to the top level menu item IDs array.
                $topLevelMenuItemIDs[] = $i;
            }
        }
    
        // Count how many top level nav items were found, so you can target the last one
        $topLevelMenuItemCount = count($topLevelMenuItemIDs);
    
        // Add a class to the last top level navigation item.
        $lastMenuItemClass = 'last-menu-item';
        $items[$topLevelMenuItemIDs[--$topLevelMenuItemCount]]->classes[] = $lastMenuItemClass;
    
        // Return the items with the altered last item
        return $items;
    }
    // Hook the last menu item class to the wp_nav_menu_objects filter
    add_filter('wp_nav_menu_objects', 'fancy_last_menu_item_class');
    
  5. Very late to the party, but was looking for something that handled doing this for submenus and subsubmenus also, ended up coming up with this, writing it out long-hand to with comments to help get my head around it.

    Basically rather than trying to loop backwards, it loops forwards and checks the next menu item parent.

     add_filter('wp_nav_menu_objects', 'first_last_nav_menu_item_classes');
    
     function first_last_nav_menu_item_classes($items) {
    
        // --- loop items to add classes ---
        $itemcount = count($items);
        $lastmenuitems = $lastsubmenuitems = $lastsubsubmenuitems = array();
        foreach ($items as $i => $item) {
    
            // --- get menu item parent ---
            $itemparent = $item->menu_item_parent;
    
            // --- maybe get next item and parent ---
            if (isset($nextitem)) {unset($nextitem);}
            if (isset($items[($i+1)])) {
                $nextitem = $items[($i+1)];
                $nextparent = $nextitem->menu_item_parent;
            }
    
            if ($itemparent == 0) {
    
                // --- clear for top level menu item ---
                if (isset($parentmenu)) {unset($parentmenu);}
                $foundsubmenu = $foundsubsubmenu = false;
    
                // --- set first menu class and store current menu index ---
                if ($i == 1) {$items[$i]->classes[] = 'first-menu-item';} else {$lastfoundmenu = $i;}
    
                // --- check if next item is a submenu of this menu ---
                if ( isset($nextitem) && ($nextparent == $item->ID) ) {$parentmenu = $item->ID;}
    
                // --- store last found submenu and subsubmenu items ---
                if (isset($lastfoundsubmenu)) {
                    $lastsubmenuitems[] = $lastfoundsubmenu;
                    unset($lastfoundsubmenu);
                }
                if (isset($lastfoundsubsubmenu)) {
                    $lastsubsubmenuitems[] = $lastfoundsubsubmenu;
                    unset($lastfoundsubsubmenu);
                }
    
            } elseif (isset($parentmenu)) {
    
                // --- subsubmenu items ---         
                if ( isset($parentsubmenu) && ($itemparent == $parentsubmenu) ) {
    
                    // --- set first subsubmenu item class ---
                    if (!$foundsubsubmenu) {
                        $items[$i]->classes[] = 'first-subsubmenu-item';
                        $foundsubsubmenu = true;
                    }
    
                    // --- store last subsubmenu item ---
                    if ( !isset($nextitem) || ( isset($nextitem) && ($nextparent != $parentsubmenu) ) ) {
                        $items[$i]->classes[] = 'last-subsubmenu-item';
                    }
    
                } elseif ($itemparent == $parentmenu) {
    
                    // --- submenu items ---
                    $foundsubsubmenu = false;
    
                    // --- add class for first submenu item ---
                    if (!$foundsubmenu) {
                        $items[$i]->classes[] = 'first-submenu-item';
                        $foundsubmenu = true;
                    }
    
                    // --- store last submenu item ---
                    if ( !isset($nextitem) || ( isset($nextitem) && ($nextparent != $parentmenu) ) ) {
                        $lastfoundsubmenu = $i;
                    }
    
                    // --- set parent submenu ---
                    if ( isset($nextitem) && ($nextparent == $item->ID) ) {$parentsubmenu = $item->ID;}
                } 
            }
        }
    
        // --- if submenu or subsubmenu item is last item ---
        if (isset($lastfoundsubmenu)) {$lastsubmenuitems[] = $lastfoundsubmenu;}
        if (isset($lastfoundsubsubmenu)) {$lastsubsubmenuitems[] = $lastfoundsubsubmenu;}
    
        // --- add classes to last found menu items ---
        $items[$lastfoundmenu]->classes[] = 'last-menu-item';
        if (count($lastsubmenuitems) > 0) {
            foreach ($lastsubmenuitems as $index) {
                $items[$index]->classes[] = 'last-submenu-item';
            }
        }
        if (count($lastsubsubmenuitems) > 0) {
            foreach ($lastsubsubmenuitems as $index) {
                $items[$index]->classes[] = 'last-subsubmenu-item';
            }
        }
    
        return $items;
     }
    

    Good inclusion to my theme, hope it helps someone looking.

Comments are closed.