It is possible to custom theme your Nice Menu's allowing the addition of icons, additional hyperlinks and a bit of HTML formatted description text right inside the menu itself giving your drop-down menu's a very rich and interactive presentation.

Please note that this is not a simple module solution and requires overriding the theme function provided in nice_menus.module. In addition, it will require a more than fair proficiency with CSS to get everything working the way you'd like.

You can view an example of the finished product at www.3xlogic.com to see what we are trying to achieve.

1) How the Nice Menu Tree and Menu Links are built by default.

Nice Menu's uses a function called theme_nice_menu_tree to build the tree of UL's and LI's that are wrapped around the Drupal core menu items. This function is found in the nice_menus.module. Within the tree, it places the Drupal menu items inside of the LI elements by calling a system function called menu_item_link($mid) as shown below. All that happens is that Nice Menu's gives menu_item_link a Menu ID # ($mid) and menu_item_link returns a formatted url wrapped in anchor tags including the Drupal menu's title as the link text and the description as the alt-title tag (the part that shows like tool-tip).

$output['content'] .= '<li id="menu-'. $mid .'" class="'. $path_class .'">'. menu_item_link($mid) .'</li>'."\n";

You can also see in the function above that Nice Menu's is adding a classname and a unique id to each LI in the format menu-mid which result in something like:

<li id="menu-192" class="menu-path-node-354><a href="/mypage">Go To My Page</a></li>

2) The Why and the How

a) There are straight CSS solutions that can be used to add a background image to an element such as the example below:

#menu-192 { background: #fff url(images/menu/management.gif) center 5% no-repeat;}

This could be used to add an icon to the menu item using only CSS and the image in this example would show up starting in the left 5% of the menu LI block and be centered vertically in the menu. The problem with this is that for menu's that have sub-menus this property is already in use to provide a little arrow or some other indicator that a sub-menu exists. To be sure, you could create images that combine the sub-menu indicator and your icon for those particular menuparents, but now it starts getting more complicated when you add or delete submenu's.

In addition, we also wanted to have the ability to add additional description text, additional hyperlinks and internal html to the menu so we can do all of this in one swoop.

The way we will do this is by creating additional containers (like div's and anchor tags) within the menu LI to hold our additional content. We will also provide classes & id's so we can target it further with CSS and we will set it up so that we can grab the description text out of the Drupal menu item and have it displayed inside of the LI as well.

3) Overriding the theme_nice_menu_tree function.

a) To create the additional containers within the list item we will simply override the output of the theme_nice_menu_tree function to output the additional containers we want. Copy the entire theme_nice_menu_tree function from the nice_menus.module file to the template.php file for your theme and change the "theme_" part of the function name to "phptemplate_".

Keep in mind that there will be no built-in CSS to format your additional containers, so until you add that these, additional items will really make your menu's look messed up.

Let's start first by adding a div called m-outer that will be within the menu LI that can serve as a container to hold everything including the preexisting anchor tag created by menu_item_link. We want to add the class="m-outer" and id="m-outer-($mid) so we can target the container with CSS.

-   $output['content'] .= '<li id="menu-'. $mid .'" class="'. $path_class .'">'. menu_item_link($mid) .'</li>'."\n";
+   $output['content'] .= '<li id="menu-'. $mid .'" class="'. $path_class .'">'. '<div class="m-outer" id="m-outer-' . $mid . '">' . menu_item_link($mid) .'</div></li>'."\n";

Next, let's add another container to hold our icon and since I plan on having this on the left side of my menu I will call it m-in-l for menu-inside-left. You will notice that it is empty .. because I only plan on using the CSS background-image property to fill it with my icon at render-time.

-   $output['content'] .= '<li id="menu-'. $mid .'" class="'. $path_class .'">'. '<div class="m-outer" id="m-outer-' . $mid . '">' . menu_item_link($mid) .'</div></li>'."\n";
+   $output['content'] .= '<li id="menu-'. $mid .'" class="'. $path_class .'">'. '<div class="m-outer" id="m-outer-' . $mid . '"><div class="m-in-l" id="m-in-l-' . $mid . '"></div>' . menu_item_link($mid) .'</div></li>'."\n";

Next I want to add a div in the right side of my menu to hold the existing anchor and other content. I will call this one m-in-r.

-   $output['content'] .= '<li id="menu-'. $mid .'" class="'. $path_class .'">'. '<div class="m-outer" id="m-outer-' . $mid . '"><div class="m-in-l" id="m-in-l-' . $mid . '"></div>' . menu_item_link($mid) .'</div></li>'."\n";
+   $output['content'] .= '<li id="menu-'. $mid .'" class="'. $path_class .'">'. '<div class="m-outer" id="m-outer-' . $mid . '"><div class="m-in-l" id="m-in-l-' . $mid . '"></div><div class="m-in-r" id="m-in-r-' . $mid . '">' . menu_item_link($mid) .'</div></div></li>'."\n";

Now I want to add a container to hold my description text / html which will go below the existing a tag. I will call it m-desc for menu description.

-   $output['content'] .= '<li id="menu-'. $mid .'" class="'. $path_class .'">'. '<div class="m-outer" id="m-outer-' . $mid . '"><div class="m-in-l" id="m-in-l-' . $mid . '"></div><div class="m-in-r" id="m-in-r-' . $mid . '">' . menu_item_link($mid) .'</div></div></li>'."\n";
+   $output['content'] .= '<li id="menu-'. $mid .'" class="'. $path_class .'">'. '<div class="m-outer" id="m-outer-' . $mid . '"><div class="m-in-l" id="m-in-l-' . $mid . '"></div><div class="m-in-r" id="m-in-r-' . $mid . '">' . menu_item_link($mid) . '<div class="m-desc" id="m-desc-' . $mid . '">' . '</div></div></div></li>'."\n";

Now .. you will notice that there is nothing inside of the description div item and we need to get it from somewhere. In this example I want to get the description text from the Drupal menu item that matches this menu's link. I am going to use a custom function I've added to the template.php file to get the text so I will add the function call here. It will return everything from the description field including any html you've entered in there. I've named my function c_menu_item_description and we pass it the menu id with the $mid variable and it simply returns the text.

-   $output['content'] .= '<li id="menu-'. $mid .'" class="'. $path_class .'">'. '<div class="m-outer" id="m-outer-' . $mid . '"><div class="m-in-l" id="m-in-l-' . $mid . '"></div><div class="m-in-r" id="m-in-r-' . $mid . '">' . menu_item_link($mid) . '<div class="m-desc" id="m-desc-' . $mid . '">' . '</div></div></div></li>'."\n";
+   $output['content'] .= '<li id="menu-'. $mid .'" class="'. $path_class .'">' . '<div class="m-outer" id="m-outer-' . $mid . '"><div class="m-in-l" id="m-in-l-' . $mid . '"></div><div class="m-in-r" id="m-in-r-' . $mid . '">' . menu_item_link($mid) . '<div class="m-desc" id="m-desc-' . $mid . '">' . c_menu_item_description($mid) . '</div></div></div>' . '</li>'."\n";

So now we get the output we want from Nice Menu's, but if you've already looked at the nice_menus.module yourself, you will see there are actually two outputs in the theme_nice_menu_tree. The only difference between them are that the first one adds the class menuparent if the item has a submenu, and the second output does not add this additional class. You have to add your code to both so here is the code for the menuparent output line:

$output['content'] .= '<li id="menu-'. $mid .'" class="menuparent '. $path_class .'">' . '<div class="m-outer" id="m-outer-' . $mid . '"><div class="m-in-l" id="m-in-l-' . $mid . '"></div><div class="m-in-r" id="m-in-r-' . $mid . '">'. menu_item_link($mid) . '<div class="m-desc" id="m-desc-' . $mid . '">' . c_menu_item_description($mid) . '</div></div></div>';

For this output line the closing tags are on the next line if you were wondering.

So now before we try to upload the file into the Nice Menu's folder and run it we have to stick the code for the custom function into our template.php file or we will get an error for an undefined function call.

Here is the custom function that gets the description text from the menu item. I know it's really complicated, but here's how it works. We use the drupal function menu_get_item to return the array of a single menu item. We only want the description part so we pull it out of the array and assign it to our $descrip variable which we then return. ( I was kidding about the complicated part you know ..)

/**
 * Returns description text of a menu item.
 *
 * @param $mid
 *   The menu item id to render.
 */
function c_menu_item_description($mid) {
  $item = menu_get_item($mid);
  $descrip = '';
  $descrip = $item['description'];
  return $descrip;
}

Simply insert that anywhere inside your theme's template.php file .. except of course in the middle of another function.

4) Can we try it now??

Well, yes. We are at a point we could actually try it out and see if our additional containers are showing up correctly in the html. To do this simply upload the edited template.php to the root of your theme folder. You might want to add a bit of css targeting the new elements so you can actually see them on the screen if they are empty.

Use Firebug to see if they are showing up as desired or if you made a mistake in the $output.

5) How about that image? Let's start with a new CSS file.

Again, the image is applied to the m-in-l container using the CSS background image property. Nice Menu's provides it's own basic CSS and also offers a way for you to specify another CSS file to override the default CSS here: http://www.your_site.com/admin/build/themes/settings. Simply enter somthing like c_nicemenus.css then create a file by that name and put it in your theme's root.

Do not enter your screen.css file as the custom CSS file or you will end up with two copies of screen.css being loaded for every page. http://drupal.org/node/465590

Also note that your original theme developer may have added Nice Menu's override CSS to the screen.css file. You simply need to delete it from screen.css and insert it into your new c_nicemenus.css file. Again .. http://drupal.org/node/465590

5) The CSS. Oww. Oww. Oww!

Ok. So you are done right? Well not even close .. everything is there but it's in all the wrong places. You now have to go through and stylize everything the way you want using your new c_nicemenus.css file. Start with Firefox, Web Developer and Firebug. Use Firebug interactively to test things out and see how it's working, then as it works add it to your c_nicemenus.css file and keep going. Get it all working in Firefox before moving onto other browsers and your life will be easier.

Here's a few tips to help you out.

This will display a test icon in all your menus. Upload a picture called test.gif to your theme image folder and size the picture to be 60px x 60px.

#block-nice_menus-1 li ul li .m-in-l {
float: left;
width: 60px;
height: 60px;
background: #fff url(images/test.gif) center center no-repeat;

Try this to make the m-outer div stretch out to hold your new containers.

#block-nice_menus-1 li ul li .m-outer {
display: block;
height: 60px;
width: 250px;
background-color: #FFFFFF;
border-right-color: #B0C4CF;
border-bottom-color: #B0C4CF;
border-left-color: #B0C4CF;
border-right-style: solid;
border-bottom-style: solid;
border-left-style: solid;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
}

Use this to give your existing link some dimension:

#block-nice_menus-1 li ul li a {
  line-height: 26px;
  background: none;
  border: none;
  clear: left;
  text-align: left;
  color: #333;
  width: 170px;
  height: 26px;

Use this to give your description container some depth.

#block-nice_menus-1 li ul li div.m-desc {
display: inline-block;
clear: left;
padding-left: 8px;
width: 170px;
height: 30px;
font-style: italic;
}

Use as many of these as you need to add specific icons to your menu's.

#block-nice_menus-1 li ul li div#m-in-l-139 { background: #fff url(images/menu/products.gif) center center no-repeat;}
#block-nice_menus-1 li ul li div#m-in-l-320 { background: #fff url(images/menu/faq.gif) center center no-repeat;}

If you don't want to hassle with icons on your third level use something like this:

#block-nice_menus-1 li ul li ul li div.m-in-l { display: none;}

Use the same technique to hide the icons and descriptions in your first level .. unless you want them of course.

#block-nice_menus-1 li div#m-desc-189, #block-nice_menus-1 li div#m-desc-58, #block-nice_menus-1 li div#m-desc-60, #block-nice_menus-1 li div#m-desc-146, #block-nice_menus-1 li div#m-desc-72, #block-nice_menus-1 li div#m-desc-152 {display:none !important;}

Of course there will be stuff that is specific to your theme that you will have to adjust as well. Have fun with that.

5) More CSS. Hack!
So now that everything is working well in Firefox do not go live. Really. Go get a Remote Access account on a site like http://www.browsercam.com and take look at what your menu's now look like in IE7, IE6, Firefox 2 and Safari. You've got more work to do!

The easiest way is to target each of these three browsers specifically. If you can fix IE7 IE6 & FF2 it will work in most everything else out there including Opera, Netscape, etc.... I personally gave up on IE5.5. It would take more than just CSS hacks and it was not worth it.

The easiest way to target specific Internet Explorer browsers is to use conditional comments that only IE recognizes in your page.tpl.php file. This will load additional CSS for only the targeted IE browser such as:

	<!--[if IE 6]>
  	<link rel="stylesheet" type="text/css" href="/sites/all/themes/northstudio/ie6.css" />
		<![endif]-->
	<!--[if IE 7]>
  	<link rel="stylesheet" type="text/css" href="/sites/all/themes/northstudio/ie7.css">
		<![endif]-->

Now create an IE6.css and an IE7.css and drop it in your theme root. At this point I will post what worked for me but be aware at this point I don't know which exact line or combination of lines actually fixed it. I have completely hacked the hack .. but it's working. If I clean it up later I'll post the results here.

For IE7 something in here helped:

/* IE7 Bug fix */
#block-nice_menus-1 li ul li div.m-in-r { 
height: 26px;
width: 170px;
zoom: 1;
}
#block-nice_menus-1 li ul li div.m-desc { 
clear: none;
float: none;
display: static;

}
#block-nice_menus-1 li ul li div a.smenu {
color: red;
zoom: 1;
display: inline;
height: 26px;
}

For IE 6 this seemed to do the trick:

#block-nice_menus-1 li ul li div.m-in-l { 
clear: none;
display: inline;
float: left;
}
#block-nice_menus-1 li ul li div.m-in-r { 
clear: none;
display: inline;
float: left;
}

For Firefox 2 (and probably 1 though I am not sure) you can simply insert the mozilla specific inline block commands since other browsers will simply ignore it:

#block-nice_menus-1 li ul li div.m-in-r {
display:-moz-inline-block;
display:-moz-inline-box;
display: table-cell;
clear: none;
}

I don't know whether moz-inline-block or box fixed the problem, but one of them did. The changing the display to table-cell seemed to help some issues I had with other browsers. So there is a little glimpse into the css hell of a project like this. If someone cleans this up please post here.

6)Those little tool-tip hover tags!

Now everything's working well and you are adding html to your menu descriptions, but you notice it is now being displayed when you hover over a menu item. Not pretty.

In order to fix this I used a patch found here: http://drupal.org/node/247904 The patch adds two new functions to the nice_menus.module which replace the system calls to menu_item_link and theme_menu_item_link which are the functions Nice Menu's calls to build the anchor tag for your main menu link. NOTE: The patch function nice_menus_item_link should actually be nice_menu_item_link since that's the one we are calling.

I didn't apply the patch directly, but simply added the two new functions and then edited my already edited theme_nice_menu-tree function to call these replacement functions instead of the system functions. Here's the relevant portions of the nice_menus.module with original, my edits, and patch applied for completeness.

+- = First edits/additions (only two), to get the icon & description functionality.
+ = Second edits/additions to remove the hover tooltip.

+  /**
+   * Returns the rendered link to a menu item.
+   *
+   * This is largely a copy of menu_item_link()
+   * http://api.drupal.org/api/function/menu_item_link/5
+   *
+   * @param $mid
+   *   The menu item id to render.
+   * @return
+   *   An HTML string of a link as rendered by theme_nice_menu_item_link().
+   */
+  function nice_menu_item_link($mid) {
+    $item = menu_get_item($mid);
+    $link_item = $item;
+    $link = '';
+ 
+    while ($link_item['type'] & MENU_LINKS_TO_PARENT) {
+      $link_item = menu_get_item($link_item['pid']);
+    }
+ 
+    return theme('nice_menu_item_link', $item, $link_item);
+  }
+ 
+  /**
+   * Themes the link to a menu item.
+   *
+   * This is largely a copy of theme_menu_item_link()
+   * http://api.drupal.org/api/function/theme_menu_item_link/5
+   *
+   * @param $item
+   *   The menu item to render.
+   * @param $link_item
+   *   The menu item which should be used to find the correct path.
+   * @return
+   *   An HTML string of a link.
+   */
+  function theme_nice_menu_item_link($item, $link_item) {
+    return l($item['title'], $link_item['path'], array(), isset($item['query']) ? $item['query'] : NULL);
+  }
  /**
* Builds the inner portion of a nice menu.
*
* @param $pid
*   The parent menu ID from which to build the items.
* @param $menu
*   Optional. A custom menu array to use for theming --
*   it should have the same structure as that returned by menu_get_menu().
* @return
*   An HTML string of properly nested nice menu lists.
*/
function theme_nice_menu_tree($pid = 1, $menu = NULL) {
  $menu = isset($menu) ? $menu : menu_get_menu();
  $output['content'] = '';

  $output['subject'] = check_plain($menu['items'][$pid]['title']);

  if ($menu['visible'][$pid]['children']) {
    // Build class name based on menu path
    // e.g. to give each menu item individual style.
    foreach ($menu['visible'][$pid]['children'] as $mid) { 
      // Strip funny symbols
      $clean_path = str_replace(array('http://', '<', '>', '&', '=', '?', ':'), '', $menu['items'][$mid]['path']);
      // Convert slashes to dashes
      $clean_path = str_replace('/', '-', $clean_path);
      $path_class = 'menu-path-'. $clean_path;
      if (count($menu['visible'][$mid]['children']) > 0) {
-       $output['content'] .= '<li id="menu-'. $mid .'" class="menuparent '. $path_class .'">'. menu_item_link($mid);
+-     $output['content'] .= '<li id="menu-'. $mid .'" class="menuparent '. $path_class .'">' . '<div class="m-outer" id="m-outer-' . $mid . '"><div class="m-in-l" id="m-in-l-' . $mid . '"></div><div class="m-in-r" id="m-in-r-' . $mid . '">'. menu_item_link($mid) . '<div class="m-desc" id="m-desc-' . $mid . '">' . c_menu_item_description($mid) . '</div></div></div>';
+      $output['content'] .= '<li id="menu-'. $mid .'" class="menuparent '. $path_class .'">' . '<div class="m-outer" id="m-outer-' . $mid . '"><div class="m-in-l" id="m-in-l-' . $mid . '"></div><div class="m-in-r" id="m-in-r-' . $mid . '">'. nice_menu_item_link($mid) . '<div class="m-desc" id="m-desc-' . $mid . '">' . c_menu_item_description($mid) . '</div></div></div>';

        $output['content'] .= '<ul>';
        $tmp = theme('nice_menu_tree', $mid);
        $output['content'] .= $tmp['content'];
        $output['content'] .= "</ul>\n";
        $output['content'] .= "</li>\n";
      }
      else {
        $output['content'] .= '<li id="menu-'. $mid .'" class="'. $path_class .'">'. menu_item_link($mid) .'</li>'."\n";
+-     $output['content'] .= '<li id="menu-'. $mid .'" class="'. $path_class .'">' . '<div class="m-outer" id="m-outer-' . $mid . '"><div class="m-in-l" id="m-in-l-' . $mid . '"></div><div class="m-in-r" id="m-in-r-' . $mid . '">' . menu_item_link($mid) . '<div class="m-desc" id="m-desc-' . $mid . '">' . c_menu_item_description($mid) . '</div></div></div>' . '</li>'."\n";
+      $output['content'] .= '<li id="menu-'. $mid .'" class="'. $path_class .'">' . '<div class="m-outer" id="m-outer-' . $mid . '"><div class="m-in-l" id="m-in-l-' . $mid . '"></div><div class="m-in-r" id="m-in-r-' . $mid . '">' . nice_menu_item_link($mid) . '<div class="m-desc" id="m-desc-' . $mid . '">' . c_menu_item_description($mid) . '</div></div></div>' . '</li>'."\n";

      }
    }
  }
  return $output;
}

7) Final thoughts.

At this point everything should work well for you but there are a few things to think about. You have now added more CSS and a whole bunch of http image calls back to the server on every page load. There are at least two things you can do to help alleviate this. The first is easy, the second should be rather easy but I have not yet gotten it to work correctly.

1) Turn on CSS aggregation and caching in your admin/settings/performance page. I am not talking about page caching which also might help, but crashes my site currently. CSS aggregation and caching does work well and should not cause any problems. When you do CSS work on your site though ... turn it off since you won't be able to see the results of your work ....

2) Use image sprites. This combines all the icon images into one file and then uses CSS position to only show the part of the image for each menu icon. This is a perfect application for sprites and I am sure I will add this functionality soon.

Even with all the http calls though ... my pages load fairly well. If you are wondering about your page performance lookup YSlow.

Have fun!

Comments

advernut’s picture

Hello,

There seems to be quite a difference in the 2x version of Nice Menus. Has anyone tried this solution in the latest release? If so, please post your code/solution.

Thanks!

Anonymous’s picture

Same here. I'd really like to do at least the "subtext, subtitle, sub title" or whatever on nice menus. Anyone able to peel back this tut and reword for Drupal 7?