HowTo: Have "first" and "last" classes on menu blocks
Often, one wants to style the first and last items in a list differently than the rest. CSS 2 provides a simple selector to do that dynamically but sadly it's not supported by Internet Explorer so most designers add a "first" and "last" class to list items where needed so that they can target them with CSS definitions.
Drupal 5 added automatically-generated "first" and "last" classes to primary and secondary links (yay!), but unfortunately did not do so for menus displayed in blocks (boo!). Fortunately, the theme system allows us to do so ourselves.
The menu is actually generated by the menu_tree() function, which is not overridable by a theme. However, that function is itself called from a theme function, so we can override that. To do so, copy and paste the following code into your template.php file:
<?php
function phptemplate_menu_tree($pid = 1) {
if ($tree = phptemplate_menu_tree_improved($pid)) {
return "\n<ul class=\"menu\">\n". $tree ."\n</ul>\n";
}
}
function phptemplate_menu_tree_improved($pid = 1) {
$menu = menu_get_menu();
$output = '';
if (isset($menu['visible'][$pid]) && $menu['visible'][$pid]['children']) {
$num_children = count($menu['visible'][$pid]['children']);
for ($i=0; $i < $num_children; ++$i) {
$mid = $menu['visible'][$pid]['children'][$i];
$type = isset($menu['visible'][$mid]['type']) ? $menu['visible'][$mid]['type'] : NULL;
$children = isset($menu['visible'][$mid]['children']) ? $menu['visible'][$mid]['children'] : NULL;
$extraclass = $i == 0 ? 'first' : ($i == $num_children-1 ? 'last' : '');
$output .= theme('menu_item', $mid, menu_in_active_trail($mid) || ($type & MENU_EXPANDED) ? theme('menu_tree', $mid) : '', count($children) == 0, $extraclass);
}
}
return $output;
}
function phptemplate_menu_item($mid, $children = '', $leaf = TRUE, $extraclass = '') {
return '<li class="'. ($leaf ? 'leaf' : ($children ? 'expanded' : 'collapsed')) . ($extraclass ? ' ' . $extraclass : '') . '">'. menu_item_link($mid, TRUE, $extraclass) . $children ."</li>\n";
}
?>The first function differs from the default only in that it directs the system to the alternate version of menu_tree(), phptemplate_menu_tree_improved(). That function works exactly like its core counterpart except that it passes an extra class to our alternate menu item theme function. (Yes, you can add parameters to a theme override function.) phptemplate_menu_item() simply tacks on the extra class if specified, or does nothing different if it isn't. Voila, first and last classes on any menu block.
Note that this code adds the first/last class to the list item, not to the link itself. That is how Drupal generally handles such classes, as it allows a CSS rule to target either the list item or the link, depending on which is needed, while putting the class on the <a> element itself would only allow a themer to style the link, not the list item.
Single menu items
Sometimes you have a (sub)menu with only one item. The above code only adds the class "first" to a menu item, regardless of the fact if it is the first item among many, or the first item of only one. This may break a theme, therefore there are 2 possibilities to cover the case, that there might only be one menu item:
#1:
Add this code
$extraclass .= $num_children == 1 ? ' last' : '';
below
$extraclass = $i == 0 ? 'first' : ($i == $num_children-1 ? 'last' : '');
This will just add a "last" class, so you might have a menu item with the class "first last".
#2:
Add this code
$extraclass = $num_children == 1 ? 'single-item' : $extraclass;
below
$extraclass = $i == 0 ? 'first' : ($i == $num_children-1 ? 'last' : '');
This will add a class "single-item", instead of "first".
