Hi there,

Here's the situation:
I've got a basic navigation using the primary links. Now one of them is a book, it's called Help.
My goal is it to mark Help as active on any page of that book (even when using aliases).

I tried to find something on this site about my issue. I did find some questions on it ... and solutions that didn't really work (for me), partly because I want to use path aliases, too. So I wrote the following code:

/* extracts the string between the first and second ", eg adams_extract_path('<a href="pathurl">pathname</a>') returns "pathurl" */
function adams_extract_path($link) {
  $path = substr(stristr($link, '"'),1);
  $pathlen = strlen($path)-strlen(stristr($path, '"'));
  return substr($path, 0, $pathlen);
}

/* returns true if and only if $child is a substring of $parent */
function adams_ischild($parent, $child) {
  return (substr_count($parent, $child) > 0);
}

/* returns true if the path in $link is a substring of $breadcrumb */
function adams_isactive($link, $breadcrumb) {
  $path = adams_extract_path($link);
  return (substr_count($link, 'active') > 0) || adams_ischild($breadcrumb, $path);
}

Now in my page.tlp.php I do something like:

  <ul>
    <?php foreach ($primary_links as $link): ?>
    <li <?php if (adams_isactive($link, $breadcrumb)) print 'class="active"' ?>"><?php print $link?></li>
    <?php endforeach; ?>
  </ul>

and everything works fine. Check http://cms.conquer-space.net, where I use it. All bookpages of Help activate the primary link. The same idea also works for Forum (forum) and Bugs (project).

So this is my solution to the problem, but it feels way too dirty. Can someone tell me a nice solution?

Extra info:
Drupal 4.6.6
Modules: book, comment, contact, forum, i18n, i18nblocks, i18nmenu, locale, menu, node, page, path, poll, poormanscron, profile, project, search, statistics, subscription, taxonomy, taxonomy_access, taxonomy_image, tracker, upload, urlfilter
Don't know if it's any help that I'm already using taxonomy.

A nice solution would neither be as dirty as my workaround nor need another module - and be able to deal with path aliases. I'm open for ideas using new modules, too, though.

Any ideas?

-- Sara

P.S. Well, I guess I could at least first extract all the paths from the breadcrumb and not search the whole breadcrumb, but that doesn't make things much better...

Comments

nedjo’s picture

in menu.inc

Sara Adams’s picture

Hi nedjo,

I already looked at that function... and a few around it, but didn't really understand it. I didn't check my own menu.inc though, and that one at least has some commenting.
So, does that function solve my using the $breadcrumb? What about extracting the path from that $link?
I'm going to try if I can make the code work with _menu_get_active_trail() now and get back to you later.

-- Sara

Sara Adams’s picture

I can't make it work. Maybe it's already too late, but anyways - this is how far I got:

function adams_isactive2($link) {
  $path = drupal_get_normal_path(adams_extract_path($link));
  $trail = _menu_get_active_trail();
  $menu = menu_get_menu();
  foreach ($trail as $mid)
  {
    $path2 = $menu['items'][$mid]['path'];
    $path3 = drupal_get_path_alias($path2);
    $path4 = drupal_get_normal_path($path);
    print $path.' '.$path2.' '.$path3.' '.$path4.' --- ';
    if (($path == $path2) || ($path == $path3)) return true;
  }
  return false;
}

and it's just loads of crap. I just don't have any idea of how the drupal code works and I don't know if I've got the time to understand how it does.

-- Sara, tired and a bit frustrated

nedjo’s picture


function adams_isactive($link) {
  $links = array();
  $trail = _menu_get_active_trail();
  foreach ($trail as $mid) {
    $item = menu_get_item($mid);
    $links[] = $item['path'];
  }
  return in_array($link, $links);
}

Sara Adams’s picture

Hi nedjo,

function menu_get_item($mid, $path = NULL, $reset = FALSE) {
  static $menu;
  if (!isset($menu) || $reset) {
    $menu = menu_get_menu();
  }
  if (isset($mid)) {
    return $menu['items'][$mid];
  }
  if (isset($path)) {
    return $menu['items'][$menu['path index'][$path]];
  }
  return array();
}

function adams_in_array($element, $array) {
  if (in_array($element, $array)) return TRUE;
  foreach ($array as $ael) {
    if ($element == drupal_get_path_alias($ael))
     return 1;
  }
  return 0;
}

function adams_isactive($link) {
  $link = adams_extract_path($link);
  $links = array();
  $trail = _menu_get_active_trail();
  foreach ($trail as $mid) {
    $item = menu_get_item($mid);
    $links[] = $item['path'];
  }
  return adams_in_array($link, $links);
}

partly works. As far as I could narrow it down it works if the page doesn't have a path alias or if I set it myself using a language-prefix (en/alias de/alias etc). With predefined aliases (eg forum project) it doesn't work, though, because the language-prefix is missing.

I inserted the code for menu_get_item because it wasn't defined in my version. Also, in_array didn't work because of path aliases.

More ideas?

-- Sara

P.S. updated adams_extract_path:

/* extracts the string between href=" and the next ", eg adams_extract_path('<a href="pathurl">pathname</a>') returns "pathurl" */
function adams_extract_path($link) {
  $path = substr(stristr($link, 'href="'), 6);
  $pathlen = strlen($path)-strlen(stristr($path, '"'));
  return substr($path, 0, $pathlen);
}
marcoBauli’s picture

Howdy, i am needing same thing

Tryed pasting your code above to template.php, and the other snippet into page.tpl.php, but the following fatal error is displayed:

Fatal error: Cannot redeclare menu_get_item() (previously declared in /home/mysite/public_html/drupal/includes/menu.inc:272) in /home/mysite/public_html/drupal/themes/mytheme/template.php

Also for you to know, theres another thread about this issue going on at http://drupal.org/node/46020

..no luck with that aproach for me neither :(

Wojtek Kruszewski’s picture

That's my quick idea. At first glance it work, but I haven't tested it.

'l' function from common.inc is used to render links, also those in menu. I modified it to compare urls (opposed to internal paths), and to give 'active' style to links that are _substring_ of current (active) address.

function l($text, $path, $attributes = array(), $query = NULL, $fragment = NULL, $absolute = FALSE, $html = FALSE) {
  $active_url = check_url(url($_GET['q']));
  $link_url = check_url(url($path, $query, $fragment, $absolute));
  //watchdog( 'link', $url );
  if ($link_url == $active_url) {
    if (isset($attributes['class'])) {
      $attributes['class'] .= ' active';
    }
    else {
      $attributes['class'] = 'active';
    }
  } else if (strpos( $active_url, $link_url) === 0 ) {
    if (isset($attributes['class'])) {
      $attributes['class'] .= ' active';
    }
    else {
      $attributes['class'] = 'active';
    }
  }

  return '<a href="'. $link_url .'"'. drupal_attributes($attributes) .'>'. ($html ? $text : check_plain($text)) .'</a>';
}

First if-block is redundant, but you might wat to use two different classes for active links, and links which urls are part of active address.

yoroy’s picture

I was looking for this too, modifying the "l" function did work.
As a designer hacking away at things, I wonder if it is 'good form' to alter functions like this?

Wojtek Kruszewski’s picture

Drupal core coders suggest to avoid modifying the core, because:
- it's easy to loose grip on those changes
- it makes having multiple sites with one Drupal installation harder (one change affects older sites)
- upgrades are nightmares
- usually there are better alternatives (an example at: http://www.nicklewis.org/node/841 )

But (as for now), I'm able to keep track on those few modifications. And now, while I'm learning Drupal, I find good programming style and habbits less important, than instant effects.

Moreover, some of such changes may find their way to mainstream core.

DISCLAIMER: these are opinions of a Drupal newbie (-:

drupalzack’s picture

Hi!
This is not related to this, but in what file should one include user defined functions (adams_isactive, adams_ischild etc.)?

And how does page.tlp.php know to call these functions? Do we have to explicitly include this file?

Sorry, still trying to understand Drupal/php.

Sara Adams’s picture

Although I'm not sure if it is the exact right place, I put those functions in template.php in the theme-directory, eg. drupal/themes/mytheme/template.php.
If template.php doesn't exist yet, create it, if it does just add your code. The file is automatically used.

-- Sara

jeffreyblove’s picture

Hi Sara

Newbie here ...
I've tried your code and it works nicely for the sub-sub-menu of my little theme, but not for the sub-menu - grrrr

I have a config that includes a primary 'foo' menu and submenu in the left nav 'foobar' (url: foo/foobar). 'foobar' has subs 'chockobar' (foo/foobar/chockobar) and 'powerbar' (foo/foobar/powerbar)

When I click on 'foobar' (url: foo/foobar), primary link 'foo' does not highlight. However, if I then click on the sub 'chockobar', 'foo' highlights nicely.

Hope you do not mind taking tme out to help - I'm very new at Drupal and php and am just now missing the intricacy that skips the first level.

cheers

marcoBauli’s picture

It seems to me more a usability bug that when clicking on a secondary link, the primary link looses it's *active* status.
This behaviour seriously misleads the final user and should be fixed.

In case you think this is worth, please add your cent to the core menu system issue just filed here.

slingeraap’s picture

Is there a way to get the primary link (or node) when a secondary link is open?

Then I can do it manually. Like "if secondary link is open, then style primary link different".

slingeraap’s picture

well, i just go throught all tables in the databse and add php to the page tpl... Things like the following. Very much work to get simple things done in Drupal... :-(

$sql = mysql_query("SELECT menu.pid FROM menu, url_alias WHERE url_alias.dst = '$alias' AND url_alias.src = menu.path ");
$row = mysql_fetch_row($sql);
$alias = $row[0];

$sql = mysql_query("SELECT url_alias.dst FROM url_alias, menu WHERE menu.mid = '$alias' AND url_alias.src = menu.path ");
$row = mysql_fetch_row($sql);
$alias = $row[0];

denver-1’s picture

Big thanks, Sara Adams. Your solution works great for me!

marcoBauli’s picture

Denver, can i ask on what Drupal version are you and which version of the snippets above did you use?..i couldn't make it work...

thx

denver-1’s picture

The most recent Drupal (4.7.3 or sth). I've just used the code listed in this topic's first post - I've copied it and pasted in my page.tpl.php file at place where my Primary links are to be seen.

daria’s picture

It works great but the home link is active to, do you see a way to limit the breadcrumb to 1 ?

Thanks

Rob T’s picture

I continue to use the patch to common.inc (through 5.3) as noted here: http://drupal.org/node/60737#comment-126546

I dislike altering core components of Drupal, but this patch is the only method I have found that works like I want every time.

Has anyone found another tried-and-true way to do this without altering core files... a method that works in numerous themes?

bjacob’s picture

Hi there,

great! The code mentioned in http://drupal.org/node/60737#comment-126546 worked very well. But I also had to add an active class to my li tags. It took me a while but I think I've found the right place:

function phptemplate_menu_links($links) {
  if (!count($links)) {
    return '';
  }
  $level_tmp = explode('-', key($links));
  $level = $level_tmp[0];
  $output = "<ul class=\"links-$level\">\n";
  foreach ($links as $index => $link) {
    $output .= '<li';

    $active_url = check_url(url($_GET['q']));
    $link_url = check_url(url($link['href'], $link['query']));

    if (strpos( $active_url, $link_url) === 0 ) {
      $output .= ' class="active"';
    }
    if (stristr($index, 'active')) {
      $output .= ' class="active"';
    }

    $output .= ">". l($link['title'], $link['href'], $link['attributes'], $link['query'], $link['fragment']) ."</li>\n";
  }
  $output .= '</ul>';

  return $output;
}

Just copy the code to your template.php and it should work. Any suggestions to improve the code?

My site: TRITUM - Björn Jacob

hardik2403’s picture

Hi,
I had a similar issue of making the primary link active when, any of its corresponding sub-links are active.

I am sure everybody must have overridden their theme_links functions to get their menus styles working.

Only two lines of code have to be added to get the active-trails working

// function found in includes/theme.inc - line 537
<?php
function phptemplate_links($links, $attributes = array(class' => 'links')) { 

....
....
// somewhere in the middle of the function
      // Already present
      if ($i == 1) {
        $class .= ' first';
      }
      if ($i == $num_links) {
        $class .= ' last';
      }
      
      // Next 2 lines added for active trails 
      
      if($link['attributes']['class'] == 'active-trail'){
        $class .= ' active';
      }
      
      // Already present	
      if (isset($link['href']) && ($link['href'] == $_GET['q'] || ($link['href'] == '<front>' && drupal_is_front_page()))) {
        $class .= ' active';    // Sets the bubble to park on the current page
      }

......
....
}
?>

Hardik

dquakenbush’s picture

This doesn't seem to address the original problem -- making a parent book's menu item active when viewing one of that book's child pages when the child page doesn't have a menu item of its own.

The desired behavior:
The menu shows the parent while the book navigation lets the user move between children. As long as the user is in the book the book's TOC remains the active menu item. It seems like this should work as-is (http://api.drupal.org/api/group/menu/6) but something odd happens with book pages that I don't understand.

Anyone have suggestions to get this to work in 6.x?

SiL3NC3_SWX’s picture

Does anyone know, how to achieve this with Drupal 8.7 and Bartik theme?

Would be nice to know! Thanx.