Primary links menu active trail - yet another method
There are quite a few posts about setting the active trail properly with primary & secondary links. In Drupal 5.x, its actually handled pretty well if you use the following code to print your primary & secondary links in page.tpl.php
<?php
print theme('links', $primary_links);
?><?php
print theme('links', $secondary_links);
?>This is all good and well, but for us themers, there's a little snag. You cannot use a generic style like a.active to hit the active menu items. By default they've got a bunch of numbers tagged onto the word "active" e.g. menu-1-3-2-active. So, basically, you have to know that menu id number to style it. Throw in 20-30 menu items, and your stylesheet is going to get seriously bloated.
The function that creates the output for the primary and secondary links is in menu.inc and is not a theme function. Hence, no simple theme override in template.php. After trolling the forums and checking out the menutrails module, I hit upon the following technique that seems to work nicely.
Step 1 - override the primary & secondary links function
Yes, you're right, you cannot do a direct theme override, because its not a theme function. But you can redefine the primary and secondary links variables by using _phptemplate_variables, before they get to page.tpl.php.
In template.php add the following code:
<?php
function _phptemplate_variables($hook, $vars = array()) {
switch ($hook) {
case 'page':
// reset primary & secondary to use our own function
$vars['primary_links'] = new_primary_links();
$vars['secondary_links'] = new_secondary_links();
break;
}
return $vars;
}
?>If you already have that function in your template.php file (quite likely), then you most likely have the case 'page' bit too. In this case just add inside that section:
$vars['primary_links'] = new_primary_links();
$vars['secondary_links'] = new_secondary_links();Step 2 - rewrite the menu function
Now we can write our own function to create the menu links. For me, the core function was good enough, except for that "active" class. So all I did was copy and paste the functions for primary and secondary links, and add in a separate "active" class. Here's the code (somewhere in template.php):
<?php
/**
* Returns an array containing the primary links - modified to add in a separate "active" class.
*/
function new_primary_links($start_level = 1, $pid = 0) {
if (!module_exists('menu')) {
return NULL;
}
if (!$pid) {
$pid = variable_get('menu_primary_menu', 0);
}
if (!$pid) {
return NULL;
}
if ($start_level < 1) {
$start_level = 1;
}
if ($start_level > 1) {
$trail = _menu_get_active_trail_in_submenu($pid);
if (!$trail) {
return NULL;
}
else {
$pid = $trail[$start_level - 1];
}
}
$menu = menu_get_menu();
$links = array();
if ($pid && is_array($menu['visible'][$pid]) && isset($menu['visible'][$pid]['children'])) {
$count = 1;
foreach ($menu['visible'][$pid]['children'] as $cid) {
$index = "menu-$start_level-$count-$pid";
if (menu_in_active_trail_in_submenu($cid, $pid)) {
$index .= "-active active"; // ************ HERE'S THE CHANGE *************
}
$links[$index] = menu_item_link($cid, FALSE);
$count++;
}
}
// Special case - provide link to admin/build/menu if primary links is empty.
if (empty($links) && $start_level == 1 && $pid == variable_get('menu_primary_menu', 0) && user_access('administer menu')) {
$links['1-1'] = array(
'title' => t('Edit primary links'),
'href' => 'admin/build/menu'
);
}
return $links;
}
/**
* Returns an array containing the secondary links - calls the new primary links function.
*/
function new_secondary_links() {
$msm = variable_get('menu_secondary_menu', 0);
if ($msm == 0) {
return NULL;
}
if ($msm == variable_get('menu_primary_menu', 0)) {
return new_primary_links(2, $msm);
}
return new_primary_links(1, $msm);
}
?>And that's it. Pretty simple really.

I'm not sure if my problem
I'm not sure if my problem was the same, but all I did was override theme_menu_item_link() by adding the following function to my template.php file (replacing THEMENAME with the name of the theme):
<?php
function THEMENAME_menu_item_link($link) {
if ($link['in_active_trail'])
$link['options']['attributes']['class'] .= ' active';
return phptemplate_menu_item_link($link);
}
?>
This is for a D6 Zen subtheme, I don't know if it would work for other cases.
Both worked for me on my 6.3
Both worked for me on my 6.3 install. I chose to go with the smaller sa001 solution.
I used the Basic theme as a starting point (which was based on Zen somewhat). There was already an existing "function THEMENAME_menu_item_link", so I just popped in these 2 lines into the template.php function...
if ($link['in_active_trail'])$link['options']['attributes']['class'] .= ' active';
This solution works great,
This solution works great, except if the menu link points to
<front>. (@sa001 this didn't work for me in D5)I can't believe this is so hard
To clarify version issues: this node is currently tagged as applying to D5, and the OP seems to indicate this explicitly above. Rob T indicates that the original snippet also worked for D5. sa001's snippet seems to apply to D6 exclusively, and did not work for me in D5 (zen starterkit).
The original snippet doesn't work directly in zen because the main (parent theme) template.php is already using _phptemplate_variables, when sub-theming they want you to use the D6-style themename_preprocess_page function.
I may come back and try to convert the logic from one format to the other, but I'm no coder. So at this point, I'm electing to look for something either simpler or more functional.
What I'm really looking for is something independent of the menu system that will highlight (set .active class) any
link on the page that is a "parent" of the current page based on the URL.In other words, that will work even if A the link isn't being generated by the menuing system and/or B the current page doesn't have a menu link itself.
Example: visiting
example.com/about/facilities/sports/tennis(could be a view, term listing, whatever) highlights (sets "active") any links pointing to:about(most likely in primary but not necessarily)about/facilities(ditto for secondary)about/facilities/sports(maybe a sidebar menu, eg html in a block)This may result in my breadcrumb trail having a mix of links classed active and not, but I can handle that with more specific CSS rules.
Any pointers would be most welcome, TIA.
Update
Found a simpler fix overriding theme_links that works with D5 and Zen, but of course this only applies to theme-generated menu links (at least it takes care of primary/secondary menus).
Cleaned-up version in my comment toward the end:
http://drupal.org/node/140491
Still looking for the more functional solution (based on the URL path rather than the menuing path as described above), any help welcome!
Direct link to hansBKK's
URLs are specific to sites, and not generic enough to be more accurate than menu_in_active_trail_in_submenu.
Direct link to hansBKK's post: http://drupal.org/node/140491#comment-931790
This is my solution with the
This is my solution with the problem.
<?php
function zen_links($links, $attributes = array('class' => 'links')) {
$output = '';
if (count($links) > 0) {
$output = '<ul'. drupal_attributes($attributes) .'>';
$num_links = count($links);
$i = 1;
foreach ($links as $key => $link) {
$class = $key;
// START NEW
if (substr($class,-6,6) == "active") {
$key = $class.' active';
}
// END NEW
// Automatically add a class to each link and also to each LI
if (isset($link['attributes']) && isset($link['attributes']['class'])) {
$link['attributes']['class'] .= ' ' . $key;
}
else {
$link['attributes']['class'] = $key;
}
// Add first and last classes to the list of links to help out themers.
$extra_class = '';
if ($i == 1) {
$extra_class .= 'first ';
}
if ($i == $num_links) {
$extra_class .= 'last ';
}
$output .= '<li '. drupal_attributes(array('class' => $extra_class . $class)) .'>';
// Is the title HTML?
$html = isset($link['html']) && $link['html'];
// Initialize fragment and query variables.
$link['query'] = isset($link['query']) ? $link['query'] : NULL;
$link['fragment'] = isset($link['fragment']) ? $link['fragment'] : NULL;
if (isset($link['href'])) {
$output .= l($link['title'], $link['href'], $link['attributes'], $link['query'], $link['fragment'], FALSE, $html);
}
else if ($link['title']) {
//Some links are actually not links, but we wrap these in <span> for adding title and class attributes
if (!$html) {
$link['title'] = check_plain($link['title']);
}
$output .= '<span'. drupal_attributes($link['attributes']) .'>'. $link['title'] .'</span>';
}
$i++;
$output .= "</li>\n";
}
$output .= '</ul>';
}
return $output;
}
?>
Just put this function in your template.php file. Just added three lines.
Little Modification for complete solution
Hi Starnox,
Just a small addition to above modification.
Starnox, your solution is elegant, however there was a little shortcoming. The code did add another class 'active' to the element 'a' (i.e. the link). However, it did not add the class 'active' as attribute to the element LI.
I just added one more line and it's now Okay. It ads 'active' to attribute class, both for element A as well as to element LI.
The final solution is here.
I just used the following fragment in template.php of my zen sub-theme directory.
// File: template.php
function zen_links($links, $attributes = array('class' => 'links')) {
$output = '';
if (count($links) > 0) {
$output = '<ul'. drupal_attributes($attributes) .'>';
$num_links = count($links);
$i = 1;
foreach ($links as $key => $link) {
$class = $key;
// START CUSTOMIZATION
// Ref: http://drupal.org/node/248522#comment-1008024
// This is to add active class to li and a href elements of links
// specifically, suited for primary links
if (substr($class,-6,6) == "active") {
$key = $class.' active';
}
$class = $key; // this line added by me.
// END CUSTOMIZATION
// Rest of the code is same as in original zen theme's template.php
feel free to post correction / improvement here.
Regards,