Complete Drupal 6 Menu Theming



Drupal 6 Theming of Menus

This tutorial expands on the snippet at: Theming Drupal Menu and how to make it work for Drupal 6.

Theming Drupal menus are easy once you understand the key drupal menu theming API functions. I will go one by one depending on the level of css selector (id/class) you want to put on drupal menu system.

Theming by adding css id/class to a Drupal Menu ul

The API function theme_menu_tree($pid) works for Drupal 5. Drupal 6 equivalent for the same is theme_menu_tree($tree). We override Drupal 5 functions by using phptemplate_menu_tree($pid) as shown in drupal menu theming example in template.php. In Drupal 6, we do that by using phptemplate_menu_tree($tree) in your template.php as in the section follow: The API function theme_menu_tree($pid) works for Drupal 5. Drupal 6 equivalent for the same is theme_menu_tree($tree)


<?php
function theme_menu_tree($tree) {
  return 
'<ul class="menu">'$tree .'</ul>';
}
?>

Now, if you want to put id foo and class bar to the menu whose name is primary-links in the ul part, you can do something like this in your template.php:


<?php
function phptemplate_menu_tree($tree) {
  if (
$tree menu_tree('primary-links')) {
    
$output .= "<ul id=\"foo\" class=\"bar\">";
    
$output .= $tree;
    
$output .= "</ul>\n";
    return 
$output;
 } else {
  return 
'<ul class="menu">'$tree .'</ul>';

 }
}
?>

Drupal menu_tree() is API function that takes name of the menu as the input and returns the tree. Sounds good?

Theming menu item li in Drupal Menu

Here you use the API function, theme_menu_item().
Here, I would add a unique id to the drupal menu theme item and then theme the menu item.


<?php
/**
 * Theme override for theme_menu_item()
 */
function phptemplate_menu_item($link$has_children$menu ''$in_active_trail FALSE$extra_class NULL) {
  
$class = ($menu 'expanded' : ($has_children 'collapsed' 'leaf'));
  if (!empty(
$extra_class)) {
    
$class .= ' '$extra_class;
  }
  if (
$in_active_trail) {
    
$class .= ' active-trail';
  }
 
  
// Add unique identifier
  
static $item_id 0;
  
$item_id += 1;
  
$id .= ' ' 'menu-item-custom-id-' $item_id;
  
// Add semi-unique class
  
$class .= ' ' preg_replace("/[^a-zA-Z0-9]/"""strip_tags($link));
 
  return 
'<li class="'$class .'" id="' $id '">'$link $menu ."</li>\n";
}
?>

<?php
function phptemplate_menu_item($link$has_children$menu ''$in_active_trail FALSE$extra_class NULL) {

if (
$menu == ''){
This is simplesincethis API function allows you to pass extra class in its function.
} else {
  
$class = ($menu 'expanded' : ($has_children 'collapsed' 'leaf'));
  if (!empty(
$extra_class)) {
    
$class .= ' '$extra_class;
  }
  if (
$in_active_trail) {
    
$class .= ' active-trail';
  }

  return 
'<li class="'$class .'">'$link $menu ."</li>\n";
 }
}
?>

You can add a zebra class like this to the menu item:


<?php
<?php
function phptemplate_menu_item($link$has_children$menu ''$in_active_trail FALSE$extra_class NULL) {
  static 
$zebra FALSE;
  
$zebra = !$zebra;
  
$class = ($menu 'expanded' : ($has_children 'collapsed' 'leaf'));
  if (!empty(
$extra_class)) {
    
$class .= ' '$extra_class;
  }
  if (
$in_active_trail) {
    
$class .= ' active-trail';
  }
  if (
$zebra) {
    
$class .= ' even';
  }
  else {
    
$class .= ' odd';
  }
  return 
'<li class="'$class .'">'$link $menu ."</li>\n";
}
?>

?>

Theming Menu link in Drupal menu

Here, you use the drupal API function theme_menu_item_link. In Drupal 6, this has become: theme_menu_item_link($link):



<?php
function theme_menu_item_link($link) {
  if (empty(
$link['localized_options'])) {
    
$link['localized_options'] = array();
  }

  return 
l($link['title'], $link['href'], $link['localized_options']);
}
?>


The way it would be used as following. In the code below, I am adding loginclass to the Login link:


<?php
function phptemplate_menu_item_link($link) {

  if (
$link['title'] == 'Login') {

  
$link['localized_options'] = array('attributes' => array('class'=>'loginclass'));
  return 
l($link['title'], $link['href'], $link['localized_options']);


} else {

if (empty(
$link['localized_options'])) {
    
$link['localized_options'] = array();
  }


  return 
l($link['title'], $link['href'], $link['localized_options']);

}
}
?>

Now you can call the menu in your page.tpl.php as:

<?php
$menu_name 
'navigation'// it is menu name. it can be primary-links etc
print menu_tree($menu_name ) ;
?>

Author: Sudeep Goyal, Drupal product development and theming




I cannot make this work at all

<?php
function phptemplate_menu_tree($tree) {
  if ($tree = menu_tree('primary-links')) {
    $output .= "<ul id=\"foo\" class=\"bar\">";
    $output .= $tree;
    $output .= "</ul>\n";
    return $output;
 } else {
  return '<ul class="menu">'. $tree .'</ul>';
 
 }
}
?>

I rewrote $tree = menu_tree('primary-links') to
$tree == menu_tree('primary-links') and still no joy.

Calls to menu_tree('primary-links') either seem to crash drupal (if I use menu_tree('navigation') as a test for example) or simply return an empty string for any other menu name. Which is a shame, because I could really do with conditionally adding different classes to the ul element...

Because the author didn't test his code.
Every time menu_tree() is called it calls phptemplate_menu_tree(), it's a infinite recursion.

Obviously

obviously, but is there any solution to this?

Hi Anonymous,

Try rebuilding your menus as follows. Run Drupal update.php or clear cache from admin/settings/performance.

You wrote this code in your theme's template.php? Correct?

Got the same problem: menu_tree('primary-links') crashes drupal.

I think that line might need to be:
menu_tree($menu_name = 'primary-links').

See http://api.drupal.org/api/function/menu_tree/6

Okay so to get the name of the menu in the theme_menu_tree function, you'll need to do something like:

<?php

/**
 * Implementation of theme_menu_tree().
 */
function THEMENAME_menu_tree($tree) {
  
$menu_name '';
  
// This is such a huge hack:
  
$backtrace debug_backtrace();
  foreach (
array_reverse($backtrace) as $call) {
    if (!empty(
$call['function']) && $call['function'] == 'menu_tree') {
      if (
is_array($call['args'])) {
        
$menu_name reset($call['args']);
        break;
      }
    }
  }

  if (
$menu_name == 'menu-footer-links') {
    
// Do something different with the footer links here:
    
return '<ul class="menu">'$tree .'</ul>';
  }
  else {
    return 
'<ul class="menu">'$tree .'</ul>';
  }
}

?>

Hi!

Your post worked!

My next problem would be the line:

<?php 
      
...
      if (
$menu_name == 'navigation-links'){ /*style here*/ }
      else return 
$tree;

?>

My main menus are all under the 'navigation' links and not the primary nor the secondary. However, it does not work because $menu_name cannot be equal to 'navigation-links'. Only 'primary-links' and secondary-links seems to be recognized. You have any idea why?

Thanks in advance

The last post works fine for me.
Just one more question : if the menu depth is sup than 1, how can i use this $menu_name only on the first level ?

Simpler way would be to create something unique in a menu item from the menu you want to modify and then preg_match it to make sure you modify the correct menu.

for example, from view source of my page on Drupal site, I described one of my primary links in $primary_links at admin/build/menus as "The Home Page of My Kiss Ass Web Site"m which outputs on the page thus:

< ... title="The Home Page of My Kiss Ass Web Site" ... >

Rewrote the snippet above thus:

<?php
function zen_ninesixty_menu_tree($tree) {
  if (
preg_match('/The Home Page of My Kiss Ass Web Site/'$tree)) {
      return 
'<ul class="sf-menu">' $tree '</ul>';
  } else {
    return 
'<ul class="menu">'$tree .'</ul>';
  } 
}
?>

It is supposed to be a comparison; Is the tree passed to the theming function equal to the tree 'primary-links':

<?php
function phptemplate_menu_tree($tree) {
  if ($tree == menu_tree('primary-links')) {
    $output .= "<ul id=\"foo\" class=\"bar\">";
    $output .= $tree;
    $output .= "</ul>\n";
    return $output;
 } else {
  return '<ul class="menu">'. $tree .'</ul>';
 
 }
}
?>

It means a bit of divitis, but you can always add block-menu-nameofmenu.tpl.php to your theme and do something like:

<div id="uniqueid">
    <?php print $block->content; ?>
</div>

Hi, How can I add database/dr_menu_links 's mlid AS menu_item id/class?

Like, if mlid = 14 ====>
<ul><li class="leaf ... mlid-14 last"> ...

Well i wanted to add an id to the top level UL of the main-menu, and this is how i did it in Drupal 7:

<?php
function mytheme_menu_tree($variables) {
    if (
$GLOBALS['custom_variable']['main-menu']['active'] ) {
        if (
$GLOBALS['custom_variable']['main-menu']['level'] == 0) {
            
$GLOBALS['custom_variable']['main-menu']['active'] = false;
            return 
'<ul class="menu" id="top">' $variables['tree'] . '</ul>';
        } else {
            
$GLOBALS['custom_variable']['main-menu']['level'] -= 1;
        }
    } 
    
    return 
'<ul class="menu">' $variables['tree'] . '</ul>';
}
?>

And

<?php
function mytheme_menu_link($variables) {

    
$element $variables['element'];
    
$sub_menu '';
    
    
//if we are in the main menu
    
if ($element['#theme'] ==  'menu_link__main-menu') {
        if (!isset(
$GLOBALS['custom_variable']['main-menu'])) {
            
$GLOBALS['custom_variable']['main-menu']['level'] =  0;
            
$GLOBALS['custom_variable']['main-menu']['active'] =  true;
        }
        
        if (
$element['#below']) {
            
$GLOBALS['custom_variable']['main-menu']['level'] += 1;
        }
    }
      
    
  if (
$element['#below']) {
    
$sub_menu drupal_render($element['#below']);
  }
  
$output l($element['#title'], $element['#href'], $element['#localized_options']);
  return 
'<li' drupal_attributes($element['#attributes']) . '>' $output $sub_menu "</li>\n";
}
?>

If a code doesn't run, it must be checked by author. I try to use this code since two hours and not successful. Harrr :(

Hello,
I am faced with a slightly tricky situation, I need to add custom css class to only first and last menu items.