In this documentation post, there is an howto of how to add an unique ID to menu items. They give the following code:

<?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";
}
?>

But this is only for Drupal 6, and I commented already if they could update it to D7, but I think they won't, because they even won't see it. But I'm on a project now, and I would be really keen to get a solution.

Probably a few little changes, but I'm not able to do it (don't even know PHP :*).

Thanks!

EDIT: I know the module "Menu Attributes", but I need to implement it in the theme, and I don't want to install a module for it.

Comments

The function "theme_menu_item" has been deprecated in Drupal 7, and it's replacement is "theme_menu_link".

The documentation for that function is here

Modifying the above code to use the new arguments:

<?php
/**
* Theme override for theme_menu_link()
* Adds a unique ID class to all menu items.
*/
function mytheme_menu_link($variables) {
 
$element = $variables['element'];
  static
$item_id = 0;
 
$element['#attributes']['class'][] = 'menu-item-custom-id' . (++$item_id);
 
$sub_menu = $element['#below'] ? drupal_render($element['#below']) : '';
 
$output = l($element['#title'], $element['#href'], $element['#localized_options']);
  return
'<li' . drupal_attributes($element['#attributes']) . '>' . $output . $sub_menu . '</li>\n';
}
?>

However, there is a comment (the first one) in the above link that works a bit better - it uses the MLID instead of a static number, which shouldn't change if you reorder menu items. Depending on your needs, that might be more useful.

Richard Lyon
Developing Drupal websites for Livelink New Media

Thanks for the reply!

I can't manage to get it work in Bartik.
I copied it in template.php, with 'mytheme' changed in 'bartik', but there's still no ID on the li's.
What can be the cause?

EDIT: I managed overriding the function in menu.inc. But now, if you have, for example two menu's below each other, with the first menu with 2 items, and the next menu 3 items, has the last item of menu 2 the class menu-item-custom-id5. What I'm trying to manage is that for each new menu, the 'menu-item-custom-id' starts with '1'. Is that possible?

Possibly, but it'd be very hacky and prone to falling over if you did it with Drupal theming I suspect.

A more robust solution (at least for those users who have JS enabled) would be adding a bit of jQuery magic. The following /should/ work:

$("ul li").each( function(i) {
// if this is an element in a submenu (and thus has been counted twice)
// skip it this time round
if($(this).parents("ul").length > 1)
return;
$(this).addClass("menu-" + i)
})

Richard Lyon
Developing Drupal websites for Livelink New Media

Thanks for your reply.

Sorry, but I'm not sure what I should do with this.
Should I add this to the theme_menu_link function or in my template.php file, or to something else?

I managed to give an ID to all items of all menu's with this piece of code in my template.php:

<?php
/**
* Returns HTML for a menu link and submenu.
*
* @param $variables
*   An associative array containing:
*   - element: Structured array data for a menu link.
*
* @ingroup themeable
*/
function mytheme_menu_link($variables) {
 
$element = $variables['element'];
 
$sub_menu = '';
  if (
$element['#below']) {
   
$sub_menu = drupal_render($element['#below']);
  }
  static
$item_id = 0;
 
$output = l($element['#title'], $element['#href'], $element['#localized_options']);
  return
'<li id="custom-menu-item-id-' . (++$item_id) . '"' . drupal_attributes($element['#attributes']) . '>' . $output . $sub_menu . "</li>\n";
}
?>

But, it is apllied to ALL menu's (mytheme_menu_link). Is there a way to apply it only to the main menu items?

I found the solution for adding the ID's only to the main menu.
Using this in your template.php will do the thing:

<?php
/**
* Returns HTML for a menu link and submenu.
*
* @param $variables
*   An associative array containing:
*   - element: Structured array data for a menu link.
*
* $num_top_level_children: counts the number of main menu items
* $item_id: number of position of current menu item
*
* @ingroup themeable
*/
function exofes_menu_link($variables) {
 
$element = $variables['element'];
 
$sub_menu = '';
  if (
$element['#below']) {
   
$sub_menu = drupal_render($element['#below']);
  }
  static
$item_id = 0;
 
$output = l($element['#title'], $element['#href'], $element['#localized_options']);
 
$num_top_level_children = count(menu_tree_page_data('main-menu'));
  if(
$item_id < $num_top_level_children) {
  return
'<li id="custom-menu-item-id-' . (++$item_id) . '"' . drupal_attributes($element['#attributes']) . '>' . $output . $sub_menu . "</li>\n";
  }
  else {
  return
'<li' . drupal_attributes($element['#attributes']) . '>' . $output . $sub_menu . "</li>\n";
  }
}
?>

Note: my main menu is the FIRST menu that appears on my screen. It's possible that for cases where the main menu is displayed after another menu (in the hierarchy of the code), this won't work.

Hope this helps others.

found a good snippet which uses the mlid in Drupal 7: http://gtsopour.blogspot.com/2011/06/add-unique-class-mlid-to-all-drupal... :

"Main and secondary menus has a great class .menu-123 (.menu-mlid) to do that. So here how you can port the same behavior to any/all menu items. Add the following function in your template.php file:"

<?php
/**
* Add unique class (mlid) to all menu items.
*/
function YOURTHEMENAME_menu_link(array $variables) {
$element = $variables['element'];
$sub_menu = '';
$element['#attributes']['class'][] = 'menu-' . $element['#original_link']['mlid'];
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";
}
?>

hello yuri,
hello all,

it drives me crazy: neither in my main-menu nor in the navigation-menu there are the menu-link-id's that you mentioned in your post. I remember, that i have seen them once, but now there is no unique id for the menu-items at all. what happened? do i have to activate them?
i am using D7.10 with a zen-subtheme.
i want to use images instead of text in my mainmenu and according to your post, with the .menu-123 class this should be easy to realize without any further plugins/tweaks.

thanks, adam

This works on Drupal 7, adapted from the original posted code. Enjoy.

First, I wrap the whole thing in an if statement because in my case I only want to adjust main-menu. If you wanted to apply it to all menus, just unwrap it.

  if(!empty($element['#original_link']['menu_name']) && $element['#original_link']['menu_name'] == 'main-menu'){

I want an ID on the parent list elements only--my submenus are going to be uniform. Again, unwrap the if statement if you want it to apply globally

if($element['#original_link']['has_children'] == 1){
/* Add the ID */
$element['#attributes']['id'][] = 'menu_'.str_replace(' ', '_',strtolower($element['#title']));
  }

If you are perfectly happy with the default naming, just delete this section--it simply loops through the values in the class part of the area and does some substitution, plus it gives a new classname to submenu items so you can control hovers etc

/* add class parent and remove leaf */
$classes_count = count($element['#attributes']['class']);
for($i=0;$i<$classes_count;++$i){
if($element['#attributes']['class'][$i] == 'expanded'){
$element['#attributes']['class'][$i] = 'parent';
}
if($element['#original_link']['plid'] == 0){
if($element['#attributes']['class'][$i] == 'leaf'){
unset($element['#attributes']['class'][$i]);
}
}
else{
if($element['#attributes']['class'][$i] == 'leaf'){
$element['#attributes']['class'][$i] = 'sub_menu';
}
}
}

Here's the full code ready for pasting into template.php. Don't forget to change the name to your theme name.

function yourThemeName_menu_link($variables) {
  $element = $variables['element'];
  static $item_id = 0;
  if(!empty($element['#original_link']['menu_name']) && $element['#original_link']['menu_name'] == 'main-menu'){
if($element['#original_link']['has_children'] == 1){
/* Add the ID */
$element['#attributes']['id'][] = 'menu_'.str_replace(' ', '_',strtolower($element['#title']));
  }
/* add class parent and remove leaf */
$classes_count = count($element['#attributes']['class']);
for($i=0;$i<$classes_count;++$i){
if($element['#attributes']['class'][$i] == 'expanded'){
$element['#attributes']['class'][$i] = 'parent';
}
if($element['#original_link']['plid'] == 0){
if($element['#attributes']['class'][$i] == 'leaf'){
unset($element['#attributes']['class'][$i]);
}
}
else{
if($element['#attributes']['class'][$i] == 'leaf'){
$element['#attributes']['class'][$i] = 'sub_menu';
}
}
}
  }
  $sub_menu = $element['#below'] ? drupal_render($element['#below']) : '';
  $output = l($element['#title'], $element['#href'], $element['#localized_options']);
  return '<li' . drupal_attributes($element['#attributes']) . '>' . $output . $sub_menu . '</li>'."\n";
}

Just wanted you to know that I tried this snippet and it resulted in an empty class (class="") for me. It turns out I found another piece of code that fitted my needs better, and sorry I don't have the PHP skills to debug this one, but thought you should know.

Try this one:

<?php
/**
* theme_menu_link()
*/
function THEMENAME_menu_link(array $variables) {
//add class for li
  
$variables['element']['#attributes']['class'][] = 'menu-' . $variables['element']['#original_link']['mlid'];
//add class for a
  
$variables['element']['#localized_options']['attributes']['class'][] = 'menu-' . $variables['element']['#original_link']['mlid'];
//dvm($variables['element']);
 
return theme_menu_link($variables);
}
?>

this one works great. thanks.