2

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:

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!

2

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.

| improve this answer | |
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
| improve this answer | |
  • not using post_parent does not work with custom menu items, this should be menu_item_parent instead as in the other answers. – majick Dec 9 '19 at 12:27
0

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');
| improve this answer | |
  • 1
    This code adds the last class to the last submenu item, not to the last top level item, which is what I understand the question to require. – s_ha_dum May 26 '13 at 14:24
0

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');
| improve this answer | |
0

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.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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