If you run a Drupal site with various sections based on node types this snippet can be useful to enhance the navigational experience of your visitors.

Drupals theme_menu_tree() function offers the classes you need to highlight active menu items. But this does not work for nodes that have no menu item assigned.

To give a real world example. I run a site with a video section. In my primary links menu I have a menu item called Videos. There are several sub menu items such as most viewed, recently added, etc. When a visitor clicks on Videos the menu is expanded. Then the visitor clicks on recently added, the menu is still expanded, that is what I want. Now when the visitor actually clicks on one if the videos listed under Videos -> recently added he or she is directed to the node the video is contained in. This node has no menu item assigned and so the menu is not expanded any more. This is not what I want.

The solution for this problem is quite easy thanks to Drupals flexibility. Here we go:

Step 1

In your template.php file add the following function:

<?php
// override theme_menu_item function
function phptemplate_menu_item($mid, $children = '', $leaf = TRUE) {
  return _phptemplate_callback('menu_item', 
  array('mid' => $mid, 'children' => $children, 'leaf' => $leaf));
}
?>

Step 2

Create a new file in your template directory called menu_item.tpl.php. This file contains the following lines of code:

<?php
$link = menu_item_link($mid);
if (arg(0) == 'node' && is_numeric(arg(1))) {
  $node = node_load(arg(1));
}
 
if ($node->type == 'video' && $mid == 158) {
  $tree = menu_tree(158);
  $children = "\n".'<ul class="menu">'. $tree ."\n".'</ul>';
  // add active class to link
  $link = str_replace('<a ', '<a class="active" ', $link);
}

$output = '<li class="'. ($leaf ? 'leaf' : ($children ? 'expanded' : 'collapsed')) .'">'. 
  $link . $children ."</li>\n";
print $output;
?>

How does this work?

Step 1 tells Drupal to override the theme_menu_item() function.

In Step 2 first we load the current menu item link and assign it to the variable $link. To get information on the node type we retrieve the current node object by passing the node id (arg(1)) to the node_load() function. Then we check whether this node is of the type video. If this returns true and the current menu id equals the id of the menu item called Videos (in my case 158) we assign the $children variable the sub menu tree of the current menu item add the active class to our link and print out the modified list item.

Now when a visitor watches a video the navigation menu is expanded and gives a clear indication that the visitor is in the video section of the web site.

Comments

felix_the_cat’s picture

Since I just needed this on a site of mine, here is how I set the menu item based on node types. I'm using Drupal 5.1, don't know if this works for 4.7

Instead of altering the menu in template.php etc., you can just use a small module and add something like this to hook_nodeapi()

function hook_nodeapi(&$node, $op) {
  switch ($op) {
    case 'view':
      if ($node->type == "story") menu_set_active_item('path/to/menu/item');
    break;
  }
}

Cheers,

Felix

colincalnan’s picture

This worked beautifully for items on the menu that were first level navigation items. However for items that were children, or in the second level the code needed to be adjusted. Here's what I did in menu_item.tpl.php

//Get current link
$link = menu_item_link($mid);

//If we're in a node load the node details
if (arg(0) == 'node' && is_numeric(arg(1))) {
  $node = node_load(arg(1));
}

//If the node is of a certain type
switch($node->type) {
	case 'something':
		if($mid == 205) {
			$tree = menu_tree(205);
 		 	$children = "\n".'<ul class="menu">'. $tree ."\n".'</ul>';
  		// add active class to link
  		$link = str_replace('<a ', '<a class="active" ', $link);
		}
		break;
		
	case 'something_elese':
		if($mid == 174) {
			$tree = menu_tree(174);
 		 	$children = "\n".'<ul class="menu">'. $tree ."\n".'</ul>';

			// add active class to child link
			$menuitem = menu_item_link(86);  // get the menu item you want to change
			$menuitem_new = str_replace('<a ', '<a class="active" ', $menuitem); // add the active class
			$children = str_replace($menuitem, $menuitem_new, $children); // now replace that link in your children with the new one
			$link = str_replace('<a ', '<a class="active" ', $link);
		}
		break;
		
	default:
		break;
}

$output = '<li class="'. ($leaf ? 'leaf' : ($children ? 'expanded' : 'collapsed')) .'">'. $link . $children ."</li>\n";
print $output;
davidwhthomas’s picture

You can also use the menutrails module to specify the parent menu item for node types and taxonomy terms. When viewing the full page view of that node, the parent menu item will be expanded and the breadcrumb trail linking back up the trail.
http://drupal.org/project/menutrails
I also submitted a patch for finer configuration control:
http://drupal.org/node/199365

psynaptic’s picture

I tried menu_trails but couldn't get it to work for a custom menu. All I wanted to do was set the active item in my custom menu based on nodes of specific content types. I tried the hook_nodeapi method and it didn't work for me. It would just display the view page with the URL of the node, not the actual node page.

I ended up doing this on the theme layer in template.php:

<?php
function _phptemplate_variables($hook, $vars = array()) {
  switch ($hook) {
    case 'node':
      $node = $vars['node'];
      switch ($node->type) {
        case 'news':
          menu_set_active_item('news');
          break;
        case 'result':
          menu_set_active_item('results');
          break;
        case 'testimonial':
          menu_set_active_item('testimonials');
          break;
        case 'partner':
          menu_set_active_item('partners');
          break;
      }
      break;
  }
}
?>

Warning: This breaks tabs on the node pages of these content types. I'm going to try and get context.module to work again.

Bartezz’s picture

I've used menu_set_active_item to change the active item in the menu to a menu item pointing to taxonomy/term/911 when viewing certain nodes. This caused my $vars['node'] to be empty in phptemplate_preprocess_page()... also my template suggestions when viewing this node became page-taxonomy.tpl.php in stead of page-node.tpl.php.

Don't know what else will be messed up by this but I imagine a lot ;) menu_set_active_item basically just changes $_GET['q'] so be very carefull when using this!!!

Cheers

________________
Live fast die young