Skip to: Site menu | Main content

Custom Menu Item Icons in Drupal

Unique icons for each menu item I recently worked on a project (based on Drupal 4.7 - but this technique works in Drupal 5 as well - more info at the end of this post) where I had to figure out the best way to assign unique icons to individual in menu within a Drupal block. As you may or may not know, in Drupal 4.7, items in item-lists are output without individual identifiers (class names or ids). This is problematic in that there is no way to the target each menu item.

The solution I came up with was to write a theme function that overrides the default "theme_item_list" function that inserts a unique value into each list item as a class name. The unique value I chose was a "cleaned" version of the item's displayed link text. For example, if the the link text is "Create Page", then the class name becomes "createpage". This way, CSS definitions can be targeted directly at individual menu items.

To start, I overrode the theme_item_list function with this:

function zen_item_list($items = array(), $title = NULL, $type = 'ul') {
$output = '<div class="item-list">';
if (isset($title)) {
$output .= '<h3>'. $title .'</h3>';
}
if (!empty($items)) {
$output .= "<$type>";
foreach ($items as $item) {
// remove all HTML tags and make everything lowercase
$css_id = strtolower(strip_tags($item));
// remove colons and anything past colons
if (strpos($css_id, ':')) $css_id = substr ($css_id, 0, strpos($css_id, ':'));
// Preserve alphanumerics, everything else goes away
$pattern = '/[^a-z]+/ ';
$css_id = preg_replace($pattern, '', $css_id);
$output .= '<li class="' . $css_id . '">'. $item .'</li>';
}
$output .= "</$type>";
}
$output .= '</div>';
return $output;
}

This function is simply a copy of the theme_item_list function from /drupal/includes/theme.inc with some additions. The added code performs the following actions to each $item:

  1. strips out all the HTML tags and converts everything to lowercase
  2. removes all numbers (this is to avoid having class names like "35 subscribers - this is just converted to "subscribers")
  3. removes any colons and anything on the right-hand side of the colon (this is to avoid anything like: "administrator: admin" - this is converted as "administrator")
  4. removes anything that is not a letter of the alphabet

Once this "cleaning" is complete, the resulting text is set at the list-item's class name. The item-list then looks something like this:

<div class="item-list">
<ul>
<li class="createpage"><a href="/~michael/drupal51sandbox/?q=node/add/page&amp;gids[]=2" title="Add a new Page in this group.">Create Page</a></li>
<li class="createstory"><a href="/~michael/drupal51sandbox/?q=node/add/story&amp;gids[]=2" title="Add a new Story in this group.">Create Story</a></li>
<li class="createvideo"><a href="/~michael/drupal51sandbox/?q=node/add/video&amp;gids[]=2" title="Add a new Video in this group.">Create Video</a></li>
<li class="invitefriend"><a href="/~michael/drupal51sandbox/?q=og/invite/2">Invite friend</a></li>
<li class="subscriber"><a href="/~michael/drupal51sandbox/?q=og/users/2/faces">1 subscriber</a></li>
<li class="manager">Manager: <a href="/~michael/drupal51sandbox/?q=user/1" title="View user profile.">michael</a></li>
<li class="mysubscription"><a href="/~michael/drupal51sandbox/?q=og/manage/2">My subscription</a></li>
</ul>
</div>

The "new" class names are fairly evident ("createpage", "createstory", etc...)

Then, using CSS, it is quite easy to assign custom icons to each menu item:

.block ul li {
list-style-image: none;
list-style-type: none;
}
div.block-og div.item-list li {
height: 18px;
padding-left: 20px;
}
div.block-og div.item-list li a {
font-size: 1em;
}
div.block-og .item-list .createpage {
background: url(images/icons/icon_case.png) no-repeat 0px 1px;
}
div.block-og .item-list .createstory {
background: url(images/icons/icon_case.png) no-repeat 0px 1px;
}
div.block-og .item-list .createvideo {
background: url(images/icons/icon_case.png) no-repeat 0px 1px;
}
div.block-og .item-list .invitefriend {
background: url(images/icons/icon_invite.png) no-repeat 0px 1px;
}
div.block-og .item-list .subscriber,
div.block-og .item-list .subscribers {
background: url(images/icons/icon_network.png) no-repeat 0px 1px;
}
div.block-og .item-list .manager {
background: url(images/icons/icon_orange.png) no-repeat 0px 1px;
}
div.block-og .item-list .mysubscription {
background: url(images/icons/icon_view.png) no-repeat 0px 1px;
}

First, any existing list-item icons are removed, then the some padding and height is added so there is room for the icons, finally, each class gets a custom icon. Note that for "subscriber" and "subscribers", they share the same icon. This is for a somewhat special case - where the menu item might read "1 subscriber" or "13 subscribers". Either way, it gets the same icon once the item title is "cleaned".

In Drupal 5, the situation is a little different - but not really. The default theme_item_list function has been improved in Drupal 5 - it allows you to set attributes for the HTML tags used to build the list. For example, you can pass in a class name for use with each "li" tag. But, it is not on a per-tag basis, so all your li tags would get the same classname.



Submitted by michael on Mon, 08/06/2007 - 10:13am
Filed under:

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

THANKS

Excellent post.
Exactly what I was looking for.

BUT- QUESTION:
HOW do I override the function?

I copied your funciton and pasted it on my template file, naming the funtion theme_item_list and I get a duplicate declaration error.

How can I specify it so that function is overriden?!
Thanks!
Rod

change the name of the function

You'll need to change the name of the function from "theme_item_list" to "the-name-of-your-theme_item_list".

So, if the name of your theme is "superduper", then you'll need to change the function name to "superduper_item_list".

-mike