This took me a while to find.

We can't use
THEME_menu_link() because it has no count and no context
THEME_menu_tree() because the child items are already rendered into HTML
THEME_preprocess_menu_tree() also only has the rendered children available
There didn't seem to be any drupal_alter I could intercept between when the menu array was built menu_tree_output() and when it was sent for rendering (eg menu_block_view()

Eventually I found that I could get at the raw list of links early enough with THEME_preprocess_menu_block_wrapper() and insert the appropriate attributes into the render array there.

/**
 * Preprocesses menus being rendered in blocks to add zebra-striping classes to
 * each menu item.
 *
 * Desired output has odd/even classes like:
 *
 * <ul class="menu">
 *   <li class="first leaf has-children menu-mlid-346 even"><a href="/racing">Racing</a></li>
 *   <li class="leaf menu-mlid-347 odd"><a href="/recreational">Recreational</a></li>
 *   <li class="leaf menu-mlid-348 even"><a href="/get-sailing">Get into Sailing</a></li>
 *   <li class="last leaf menu-mlid-349 odd"><a href="/clubs">Clubs</a></li>
 * </ul>
 */
function THEME_preprocess_menu_block_wrapper(&$variables, $hook) {
  $zebra = 0;
  foreach (element_children($variables['content']) as $mlid){
    $variables['content'][$mlid]['#attributes']['class'][] = ($zebra % 2) ? 'even' : 'odd';
    $zebra++;
  }
}

Comments

pgsengstock’s picture

The even/odd classes are backwards in this code.

($zebra % 2) ? 'odd' : 'even'

Should be:

($zebra % 2) ? 'even' : 'odd'

Fixed code block, for convenience:

<?php
/**
 * Preprocesses menus being rendered in blocks to add zebra-striping classes to
 * each menu item.
 *
 * Desired output has odd/even classes like:
 *
 * <ul class="menu">
 *   <li class="first leaf has-children menu-mlid-346 even"><a href="/racing">Racing</a></li>
 *   <li class="leaf menu-mlid-347 odd"><a href="/recreational">Recreational</a></li>
 *   <li class="leaf menu-mlid-348 even"><a href="/get-sailing">Get into Sailing</a></li>
 *   <li class="last leaf menu-mlid-349 odd"><a href="/clubs">Clubs</a></li>
 * </ul>
 */
function THEME_preprocess_menu_block_wrapper(&$variables, $hook) {
  $zebra = 0;
  foreach (element_children($variables['content']) as $mlid){
    $variables['content'][$mlid]['#attributes']['class'][] = ($zebra % 2) ? 'even' : 'odd';
    $zebra++;
  }
}
?>

UPDATE: fixed original code above.

crispymix’s picture

Don't think it's made clear here, but to do this you need to be using the menu_block module for your menus!

Also, it doesn't take into account sub-menus - here's my take:

function THEME_preprocess_menu_block_wrapper(&$variables, $hook) {
  stripe_children($variables['content']);
}

function stripe_children(&$elements) {
  $zebra = 0;
  foreach (element_children($elements) as $mlid){
    $elements[$mlid]['#attributes']['class'][] = ($zebra % 2) ? 'even' : 'odd';
    if(count(element_children($elements[$mlid]['#below'])>0)) {
      stripe_children($elements[$mlid]['#below']);
    }
    $zebra++;
  }
}