Last updated August 23, 2009. Created by mr.baileys on April 23, 2007.
Edited by SLIU, bekasu, bryan kennedy, add1sun. Log in to edit this page.
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".
Comments
If you're using Nice Menus (6.x)
This patch is PERFECT!!! I have to spread it around because it has brought me and others so much peace and joy :)
http://drupal.org/node/301247#comment-1497198
Add "single-item" class to menu leaf items in Drupal 6 themes
The menu system in Drupal 6 adds the following classes to a menu item: if it's the first leaf of a branch: "leaf first", and these to the last leaf: "leaf last". However, when the item is the one and only leaf, it still gets classed as "leaf last".
If you want to fix this, you can use the function below in your template.php file. This will change the "leaf last" classes to "leaf single-item" if you're dealing with the only item in a branch.
<?php
function my_theme_menu_item($link, $has_children, $menu = '', $in_active_trail = FALSE, $extra_class = NULL) {
static $my_theme_menu_item_leaf_counter = 0;
$class = ($menu ? 'expanded' : ($has_children ? 'collapsed' : 'leaf'));
if ($class == "leaf" ) {
$my_theme_menu_item_leaf_counter ++;
if ($extra_class == "last") {
if ($my_theme_menu_item_leaf_counter == 1) $extra_class = "single-item";
else $my_theme_menu_item_leaf_counter = 0;
}
}
if (!empty($extra_class)) {
$class .= ' '. $extra_class;
}
if ($in_active_trail) {
$class .= ' active-trail';
}
return '<li class="'. $class .'">'. $link . $menu ."</li>\n";
}
?>
@postrational FYI... This
@postrational FYI... This code doesn't seem to work. No errors, just doesn't work. I still get a menu item with first and last an no single-item.
Let me know if you have a fix. This would be great when you are styling a dropdown menu like Superfish.
fixed illegal offset type warning
We were getting an illegal offset type warning with the original code because PHP apparently wants the $pid as a string not an integer. Also made the conditional for $output at the end a little easier on the eyes to read.
function phptemplate_menu_tree_extra($pid = 1) {
$menu = menu_get_menu();
$output = '';
// wants the $pid as a string not an integer. PHP complains if this is not so.
$new_pid = (string) $pid;
if (isset($menu['visible'][$new_pid]) && isset($menu['visible'][$new_pid]['children'])) {
$num_children = count($menu['visible'][$new_pid]['children']);
for ($i=0; $i < $num_children; ++$i) {
$mid = $menu['visible'][$new_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 .= (($type && MENU_EXPANDED) || (menu_in_active_trail($mid))) ? theme('menu_item_extra', $mid, theme('menu_tree', $mid), count($children) == 0, $extraclass) : theme('menu_item_extra', $mid, '', count($children) == 0, $extraclass);
}
}
return '<ul class="menu links">'.$output.'</ul>';
}
~~~
HigherVisibility