admin管理员组

文章数量:1410737

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!

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!

Share Improve this question asked May 26, 2013 at 13:01 Alec RustAlec Rust 4161 gold badge7 silver badges20 bronze badges
Add a comment  | 

5 Answers 5

Reset to default 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.

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

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');

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');

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.

本文标签: Adding first and last class to Menu on top level only