Theming Local Task Tabs in Drupal 6

Posted on: 2010-05-14 16:44:52

In one of the primary applications on our intranet, we use small icons in the process to help our Agents and Home office users through the process.

Agents see something like this:

While our employees see something more along the lines of this:

There are various combinations but the point that I'd like to impress is that a use-case exists for theming Drupal's MENU_LOCAL_TASK tabs.

In Drupal 5, you could get away with doing stuff like this in hook_menu:

'node/'.arg(1).'/reject', 'title' => 'Reject', 'type' => MENU_LOCAL_TASK, 'callback' => 'drupal_get_form', 'callback arguments' => array('pcf_casetracker_form_reject',$node), 'access' => $finish_access, 'weight' => 3, 'class' => 'hasicon reject'); // Special class for my my tab. ?>

Then, a simple theme override:

'. menu_item_link($mid) .\"\\n\"; } else { return ''. menu_item_link($mid) .\"\\n\"; } } ?>

Would give you the intended results.

This, however, does not work in Drupal 6. There are two reasons:

Firstly, in Drupal 6, two theme functions are used to build links to menu tabs:

menu_item_link takes the actual menu router item as a parameter. It returns an HTML link. menu_local_task takes just the link, wraps it with an <li> tag, and adds the appropriate class if it is $active. At no time does the $menu_router item get passed to the function where it could affect the display of the <li> tag.

Secondly, the menu router system stores all of its values in a table called... menu_router. Writing entries to this table strips them of any values which are not in the table to begin with. So adding 'css_class' ?> in the menu's item in hook_menu() does nothing.

So how do we do this? I've got a hack, and a possible "fix."

h3. The Hack

In the menu system, and can be utilized to pass extra parameters to your page and access callbacks. These arguments get serialized before they get sent to the database. So you can actually stick a bunch of stuff in here. So, if you write your own access callback to only utilize the first param, you can stick extra information on those callback arguments like so:

'Reject', 'type' => MENU_LOCAL_TASK, ... 'access callback' => 'pcf_casetracker_can_finish', 'access arguments' => array(1, array('class' => 'hasicon reject')), ); ?>

And then simply override your theme callbacks to do some trickery. Basically, test for that extra set of classes and build the link and the item entry in theme('menu_item_link') instead of building it in theme('menu_local_task'). Then, if menu_local_task detects the '<li' at the beginning, it will just let it pass through. Now your <li> tags can have extra css or attributes passed to them.

'. $link .\"\\n\"; return $active ? str_replace('class=\"', 'class=\"active ', $link) : $link; } function garland_menu_item_link($link) { if (empty($link['localized_options'])) { $link['localized_options'] = array(); } if ($link['access_arguments'] && ($stuff = unserialize($link['access_arguments'])) && is_array($stuff) && ($b = array_pop($stuff)) && is_array($b)) { if ($b['class']) { $link['class'] = $b['class']; } } if ($link['class']) { return '
  • '. l($link['title'], $link['href'], $link['localized_options']) .\"
  • \\n\"; } return l($link['title'], $link['href'], $link['localized_options']); } ?>

    h3. The "Fix"

    Since Drupal 6 isn't taking any new features, it is highly unlikely that this will get fixed. At any rate, by modifying core to add two fields 'theme callback' and 'theme arguments', the menu system can be modified to add support for theming the individual items as they come out. From there, it is easy. One particular function, menu_local_tasks is responsible for actually rendering the links.

    By modifying the function to look for the theme function and call it if it exits, we can do all sorts of cool things. The patch is down a the bottom of this post. If there is no , it will fall back to the current method it uses.

    It might be more worthwhile to split the actual rendering and collection of the tab information into two separate functions. This is probably the better way to do it. Also, there might be a better way to do it in D7.

    Also, if you are using the Chaos tool suite you'd need to patch it as well (if you are using Garland).

    There is also probably a way to do this that involves overriding the menu theme function just like ctools does it. The only problem that still remains is making sure that the menu tabs get the proper data associated with it. There doesn't seem to be a no-brainer to attach that data after the fact. Could be wrong, though!

    This is what the code in the new solution looks like (in your module, that is.)

    The menu item itself:

    'Void', 'type' => MENU_LOCAL_TASK, 'page callback' => 'drupal_get_form', 'page arguments' => array('pcf_casetracker_form_void', 1), 'access callback' => 'pcf_casetracker_can_void', 'access arguments' => array(1, array('class' => 'hasicon void',)), 'theme callback' => 'pcf_casetracker_tab', 'theme arguments' => array('class' => 'hasicon void'), 'weight' => 9,); ?>

    The callback, which is basically theme('menu_item_link') embedded in a tweaked copy of theme('menu_local_task').

    0 ? ' class=\"'. implode(' ', $classes) .'\"' : '') .'>'. l($menu_item['title'], $menu_item['href'], $menu_item['localized_options']) .\"\\n\"; } ?>

    Anyway, hope this helps someone.