User login

Add a span wrapper inside primary and secondary menu links in Drupal 6

This document takes us through the steps of figuring out how some output is produced (without an Integrated Development Environment with debugger) so that we can modify it. Skip straight to Agaric's answer in the resolution at the bottom if that's all you want!

theme_menu_item_link() has no effect on primary / secondary navigation links (at least when printed through the theme, and not as blocks). Which was unexpected.

However, in a Zen subtheme (called examplezen here) the following code produced all kinds of interesting output for node menus and

<?php
/**
 * Generate the HTML representing a given menu item ID.
 *
 * An implementation of theme_menu_item_link()
 *
 * @param $link
 *   array The menu item to render.
 * @return
 *   string The rendered menu item.
 */
function examplezen_menu_item_link($link) {
  if (empty($link['options'])) {
    $link['options'] = array();
  }
$test = '<pre>'. var_export($link,TRUE) .'</pre>';
  // If an item is a LOCAL TASK, render it as a tab
  if ($link['type'] & MENU_IS_LOCAL_TASK) {
    $link['title'] = '<span class="tab">' . check_plain($link['title']) . '</span>';
    $link['options']['html'] = TRUE;
  }

  if (empty($link['type'])) {
    $true = TRUE;
  }

  return $test . l($link['title'], $link['href'], $link['options']);
}
?>

Here's some of that exciting test code, from admin_menu:

array (
  'load_functions' => '',
  'to_arg_functions' => '',
  'access_callback' => 'user_access',
  'access_arguments' => 'a:1:{i:0;s:22:"administer permissions";}',
  'page_callback' => 'user_admin_access',
  'page_arguments' => 'a:0:{}',
  'title' => 'Access rules',
  'title_callback' => 't',
  'title_arguments' => '',
  'type' => '6',
  'description' => 'List and create rules to disallow usernames, e-mail addresses, and IP addresses.',
  'menu_name' => 'admin_menu',
  'mlid' => '201',
  'plid' => '199',
  'link_path' => 'admin/user/rules',
  'router_path' => 'admin/user/rules',
  'link_title' => 'Access rules',
  'options' =>
  array (
    'alter' => true,
  ),
  'module' => 'admin_menu',
  'hidden' => '0',
  'external' => '0',
  'has_children' => '1',
  'expanded' => '0',
  'weight' => '0',
  'depth' => '2',
  'customized' => '0',
  'p1' => '199',
  'p2' => '201',
  'p3' => '0',
  'p4' => '0',
  'p5' => '0',
  'p6' => '0',
  'p7' => '0',
  'p8' => '0',
  'p9' => '0',
  'updated' => '0',
  'in_active_trail' => false,
  'href' => 'admin/user/rules',
  'access' => true,
  'localized_options' =>
  array (
    'alter' => true,
    'alias' => true,
  ),
)</pre><a href="/admin/user/rules">Access rules</a>

And from the navigation menu:

<li class="leaf first"><pre>array (
  'load_functions' =>
  array (
    1 => 'user_uid_optional_load',
  ),
  'to_arg_functions' => 'a:1:{i:1;s:24:"user_uid_optional_to_arg";}',
  'access_callback' => 'user_view_access',
  'access_arguments' => 'a:1:{i:0;i:1;}',
  'page_callback' => 'user_view',
  'page_arguments' => 'a:1:{i:0;i:1;}',
  'title' => 'My account',
  'title_callback' => 'user_page_title',
  'title_arguments' => 'a:1:{i:0;i:1;}',
  'type' => '6',
  'description' => '',
  'menu_name' => 'navigation',
  'mlid' => '21',
  'plid' => '0',
  'link_path' => 'user/%',
  'router_path' => 'user/%',
  'link_title' => 'My account',
  'options' =>
  array (
  ),
  'module' => 'system',
  'hidden' => '0',
  'external' => '0',
  'has_children' => '0',
  'expanded' => '0',
  'weight' => '0',
  'depth' => '1',
  'customized' => '0',
  'p1' => '21',
  'p2' => '0',
  'p3' => '0',
  'p4' => '0',
  'p5' => '0',
  'p6' => '0',
  'p7' => '0',
  'p8' => '0',
  'p9' => '0',
  'updated' => '0',
  'in_active_trail' => false,
  'href' => 'user/1',
  'access' => true,
  'localized_options' =>
  array (
  ),
)</pre><a href="/user/1">My account</a></li>

But nothing for the primary links:

                    <div id="primary">

                <ul class="links"><li class="menu-115 first"><a href="/" title="Example front page.">Home</a></li>
<li class="menu-117 last"><a href="/forum" title="Participate in discussions about Example.">Community Forums</a></li>
</ul>              </div> <!-- /#primary -->

There's a <href="http://api.drupal.org/api/function/menu_primary_links/6">menu_primary_links() function:

<?php
function menu_primary_links() {
  return menu_navigation_links(variable_get('menu_primary_links_source', 'primary-links'));
}
?>

But there is no theming in the function it calls, menu_navigation_links.

So this all must be a level above theme_links which gets handed the menu array (somehow) and in this big huge function below throws the HTML around it, here, where we have put in some test code:

<?php
function examplezen_links($links, $attributes =  array('class' => 'links')) {
  $output = '';
$test .= '<pre>'. var_export($links,TRUE) .'</pre>';
  if (count($links) > 0) {
    $output = '<ul'. drupal_attributes($attributes) .'>';

    $num_links = count($links);
    $i = 1;

    foreach ($links as $key => $link) {
      $class = $key;

      // Add first, last and active classes to the list of links to help out themers.
      if ($i == 1) {
        $class .= ' first';
      }
      if ($i == $num_links) {
        $class .= ' last';
      }
      if (isset($link['href']) && ($link['href'] == $_GET['q'] || ($link['href'] == '<front>' && drupal_is_front_page()))) {
        $class .= ' active';
      }
      $output .= '<li'. drupal_attributes(array('class' => $class)) .'>';

      if (isset($link['href'])) {
        // Pass in $link as $options, they share the same keys.
        $output .= l($link['title'], $link['href'], $link);
      }
      else if (!empty($link['title'])) {
        // Some links are actually not links, but we wrap these in <span> for adding title and class attributes
        if (empty($link['html'])) {
          $link['title'] = check_plain($link['title']);
        }
        $span_attributes = '';
        if (isset($link['attributes'])) {
          $span_attributes = drupal_attributes($link['attributes']);
        }
        $output .= '<span'. $span_attributes .'>'. $link['title'] .'</span>';
      }

      $i++;
      $output .= "</li>\n";
    }

    $output .= '</ul>';
  }

  return $test . $output;
}
?>

The output, with test code output preceding it, looks like this:

                          <div id="primary">

                <pre>array (
  'menu-115' =>
  array (
    'attributes' =>
    array (
      'title' => 'Example front page.',
    ),
    'href' => '<front>',
    'title' => 'Home',
  ),
  'menu-117' =>
  array (
    'attributes' =>
    array (
      'title' => 'Participate in discussions about Example.',
    ),
    'href' => 'forum',
    'title' => 'Community Forums',
  ),
)</pre><ul class="links"><li class="menu-115 first"><a href="/" title="Example front page.">Home</a></li>
<li class="menu-117 last"><a href="/forum" title="Participate in discussions about Example.">Community Forums</a></li>
</ul>              </div> <!-- /#primary -->

Resolution

All I want to do is throw spans around the links. Ah well. Here is the function below, modified to do exactly that.

It will do it to all sets of links passed through theme('links', $links) but so far on Agaric's example site, at least, that means only the primary links.

<?php
function examplezen_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;

      // Add first, last and active classes to the list of links to help out themers.
      if ($i == 1) {
        $class .= ' first';
      }
      if ($i == $num_links) {
        $class .= ' last';
      }
      if (isset($link['href']) && ($link['href'] == $_GET['q'] || ($link['href'] == '<front>' && drupal_is_front_page()))) {
        $class .= ' active';
      }
      $output .= '<li'. drupal_attributes(array('class' => $class)) .'>';

      if (isset($link['href'])) {
        $link['title'] = '<span class="link">' . check_plain($link['title']) . '</span>';
        $link['html'] = TRUE;     
        // Pass in $link as $options, they share the same keys.
        $output .= l($link['title'], $link['href'], $link);       
      }
      else if (!empty($link['title'])) {
        // Some links are actually not links, but we wrap these in <span> for adding title and class attributes
        if (empty($link['html'])) {
          $link['title'] = check_plain($link['title']);
        }
        $span_attributes = '';
        if (isset($link['attributes'])) {
          $span_attributes = drupal_attributes($link['attributes']);
        }
        $output .= '<span'. $span_attributes .'>'. $link['title'] .'</span>';
      }

      $i++;
      $output .= "</li>\n";
    }

    $output .= '</ul>';
  }
  return $output;
}
?>

Searched words: 
span wrapper for primary links make tab-style links for main navigation menu

Comments

Thanks!

It was the easiest-to-follow effective tutorial I've read! now I just have to implement...

great! Just gimme the code

great!

Just gimme the code next time though, god!

I just want answers! :P

I am a complete neophyte to

I am a complete neophyte to php. In fact I am trying to use Drupal as momentum to learn more php.

My question is where would I actually place the above code in order to implement the span tags around menu text?

Would I implement this as a patch? (I saw a tutorial about how to implement a patch, so I don't think that would be a problem.)

Thanks, Malik

In template.php of your theme

The function needs to be prefaced with the name of your theme (replacing "examplezen") or phptemlpate_

If you do not have a template.php in your theme directory you can create it and put this function there.

No patch needed!

(Generally, only patch to test out people's code changes, and report back to them if their change worked. Then the change can go into the official distribution everyone uses. If you think you can apply and test patches, that is a great way to support Drupal.)

:)

You. ROCK!

Jumped straight to resolution... and the code worked immediately. Briefly scanned the rest and may return to read it at some point. :) But thank you so much for your work on this, which does exactly what I was looking for... fabulous.

What about the other links

How do I go about getting the span in all my links that are generated through lists so that they appear as:

<li><a href = "something.html"><span>Something</span></a></li>

on a side note it took me 10 tries before it accepted the Captcha, even though I KNOW I filled it out correctly each time.

Thanks so much! This is

Thanks so much! This is EXACTLY what was needed for a client of mine.

Some minor issues

Just to point that I've had some issues with similar example. Language Icons module uses another span wrapper to display country flags along with the language name so things got messy. Replacing check_plain() with decode_entities() made everything to look as it was indented.

Could this be a better method

You may have been closer than you thought with the initial function "examplezen_menu_item_link" at the top of your article. I took this function and modified it slightly as shown below.

<?php

function examplezen_menu_item_link($link) {
  if (empty($link['options'])) {
    $link['options'] = array();
  }
  // modify $link['title'] to include span tags and use check plain() to strip out any undesired markup.
  $link['title'] = $link['title'] = '<span class="link">' . check_plain($link['title']) . '</span>';
  // modify the options array to include an html field set to true.
  $link['options'] += array('html'=> TRUE);
  return l($link['title'], $link['href'], $link['options']);
}
?>

The subtle change is replacing the line $link['options']['html'] = TRUE; with $link['options'] += array('html'=> TRUE);. I also removed all the test output so the above code should work as is, just remember to change "examplezen" to the name of your theme.

Hope this is helpful.

If the menu in question goes through theme_menu_item_link

Thanks beadysea! If the menu to alter goes through theme_menu_item_link, then that will work great. Unfortunately that's not always the case.

A wonderful article you

A wonderful article you posted. That is so informatory and creative.

:\

Ben,

Thanks for the code! Everything looks like it should work but - i'm having trouble and i don't know why! Save me! :)

i copied your code into my previously empty template.php file (my theme is entirely custom), and changed "function examplezen_links" to "function v1_links", as "v1" is my theme name.

i have my primary links displayed in a block.

Any ideas as to where i'm going wrong?

Thanks!

Thanks

Thank you for this, I was just about to spend God knows how long writing this myself. You saved me a bunch.

Thanks again. I'll report any errors I encounter from using it.

Thanks

I wrestled the whole evening trying to get a

<

div class="rss-icon"> around a set of feed menu items, your first few lines of code with the set me on the right track. In fact, that $link['options']['html'] = TRUE did the trick for me.

Thanks again!

Frank.

D7 Updates

Does anybody know how to updates this top D7 cause this feature ist still missing I guess.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • You may post code using <code>...</code> (generic) or <?php ... ?> (highlighted PHP) tags.
  • You can use Markdown syntax to format and style the text. Also see Markdown Extra for tables, footnotes, and more.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <img> <blockquote> <small> <h2> <h3> <h4> <h5> <h6> <sub> <sup> <p> <br> <strike> <table> <tr> <td> <thead> <th> <tbody> <tt> <output>
  • Lines and paragraphs break automatically.

More information about formatting options

By submitting this form, you accept the Mollom privacy policy.