I want to add a 'first' and 'last' tag to the MENU_LOCAL_TASK tabs so I can add rounded corners to them. Here's the function that sets up the tabs -

function theme_menu_local_tasks() {
  $output = '';

  if ($primary = menu_primary_local_tasks()) {
    $output .= "<ul class=\"tabs primary\">\n". $primary ."</ul>\n";
  }
  if ($secondary = menu_secondary_local_tasks()) {
    $output .= "<ul class=\"tabs secondary\">\n". $secondary ."</ul>\n";
  }

  return $output;
}

So basically I need to add first and last tags to the relevent "li" items in $primary. Here is what the $primary variable looks like -

<li class="active" ><a href="/drupal-6.10/user/5" class="active"><span class="tab">View</span></a></li>
<li ><a href="/drupal-6.10/user/5/edit"><span class="tab">Edit</span></a></li>
<li ><a href="/drupal-6.10/user/5/guestbook"><span class="tab">My guestbook</span></a></li>
<li ><a href="/drupal-6.10/user/5/devel/load"><span class="tab">Dev load</span></a></li>
<li ><a href="/drupal-6.10/user/5/devel/render"><span class="tab">Dev render</span></a></li>

So any php string wizards out there able to tell me how to add a "class=first" and "class=last" in the appropriate places?

Comments

marcvangend’s picture

The li element is rendered by the theme_menu_local_task function (http://api.drupal.org/api/function/theme_menu_local_task/6) so in theory, the proper way to do this would be to override that theme function. Unfortunately, that function has no way to 'know' if it is rendering the first, last, or another item. Is it acceptable for you to solve this with javascript? I guess that would be the easiest solution.

ltwinner’s picture

I would like to do it without javascript if possible. I know there is only a small minority with javascript disabled these days however for something as 'trivial' as rounded corners on these tabs I don't want to have to have a javascript setting and a non-javascript setting.

You are correct in that there is no way of knowing which is first and which is last with regards to theme_menu_local_task(). However in the $primary string that is constructed from calls to theme_menu_local_task() it is clear enough that if you search for the first occrurance of <li from the start of the $primary string you will have the location of where to insert the "class='first'" tag. And if you search back from the end of the string for, again, the first occrurance of <li you will have the location for the "last" tag. I'm not that familiar with string handling in php unfortunately. That's why I'd appreciate help off someone who know's their php string coding.

marcvangend’s picture

You are right, it's what I would consider an ugly hack, but you can add the classes with find-and-replace code. However, it's a little more complicated than you write, because the li tag can already have a class="active". This is how I would do it:

// split into an array
$items = (explode("/li>\n<", menu_primary_local_tasks())); 

// process the first item
if(strpos($items[0], 'li class=') !== FALSE) { // does it have a class yet?
  $items[0] = str_replace('li class="', 'li class="first ', $items[0]);
} else {
  $items[0] = str_replace('li', 'li class="first"', $items[0]);
}

// process the last item
$lastindex = count($items) - 1;
if(strpos($items[$lastindex], 'li class=') !== FALSE) {
  $items[$lastindex] = str_replace('li class="', 'li class="last ', $items[$lastindex]);
} else {
  $items[$lastindex] = str_replace('li', 'li class="last"', $items[$lastindex]);
}

// create a string from the array
$output = implode("/li>\n<", $items);

I assume you know yourself how to implement this code in a theme override.

ltwinner’s picture

works great, thanks alot man!

marcvangend’s picture

Cheers, thanks for the feedback.

jaypan’s picture

Thanks Marc! I couldn't figure out how to do this for a long time. I found your post, but I wasn't sure where to place that code that you created, so I used it, and re-wrote it a little as a preprocess function in my theme. Here is the code for anyone else who wants to use it (place it in template.php in your theme folder):

<?php
function phptemplate_preprocess_page(&$vars)
{
	$tabs = $vars['tabs'];
	$items = explode("\n", $tabs);
	$opening_ul = array_shift($items);
	array_pop($items);
	if(strpos($items[count($items) - 1], '</ul>') !== FALSE)
	{
		array_pop($items);
	}
	
	// process the first item
	if(strpos($items[0], 'li class=') !== FALSE) // does it have a class yet?
	{
		$items[0] = str_replace('li class="', 'li class="first ', $items[0]);
	}
	else
	{
		$items[0] = str_replace('li', 'li class="first"', $items[0]);
	}

	// process the last item
	$lastindex = count($items) - 1;
	if(strpos($items[$lastindex], 'li class=') !== FALSE)
	{
		$items[$lastindex] = str_replace('li class="', 'li class="last ', $items[$lastindex]);
	}
	else
	{
		$items[$lastindex] = str_replace('li', 'li class="last"', $items[$lastindex]);
	}
	$items = implode(PHP_EOL, $items);
	// create a string from the array
	$vars['tabs'] = $opening_ul . PHP_EOL . $items . PHP_EOL . '</ul>';
}
?>

Contact me to contract me for D7 -> D10/11 migrations.

bradleyvance’s picture

I've implemented Jay's code, and it almost works, but not quite. It does mark one tab as ".first" and one as ".last", but often times its the second tab that is marked as .first. Is anyone else having this issue?

I've tried it in a Zen based theme and Garland as well, same result in both.

jaypan’s picture

Can you do the following:
1) Post up your code (unless it's exactly a copy and paste of mine, then just leave it.
2) Find a page where the 2nd tab is getting the .last class. Load that page, then add this line of code:

die('<pre>' . print_r($items, true) . '</pre>');
after this code:

<?php
    $tabs = $vars['tabs'];
    $items = explode("\n", $tabs);
    $opening_ul = array_shift($items);
    array_pop($items);
    if(strpos($items[count($items) - 1], '</ul>') !== FALSE)
    {
        array_pop($items);
    }
?>

Refresh your page, then copy the output to here, and I'll take a look at it.

Contact me to contract me for D7 -> D10/11 migrations.

bradleyvance’s picture

Thanks for that line of code. Now I can sort of see where the problem, here's what it output:

Array
(
    [0] => Addresses
#

    [1] => Bookmarks
#

    [2] => Edit
#

    [3] => Files
#

    [4] => Orders
#

    [5] => Track page visits
#

)

The problem is that onscreen (and in my HTML) "view" is the 1st tab in the list, and it doesn't even show up here. So that must be why "Adresses" keeps getting marked as .first. But why wouldn't "view" show up?

Also, is there anyway to exclude the secondary tabs?

Thanks for the help by the way!

jaypan’s picture

That's strange. Can you adding the 'die' command right after this line:

$tabs = $vars['tabs'];
and then copying the output here? It shouldn't really be happening that way.

And yes, there is a way to exclude secondary tabs - I had to do it on a site at work. But I wont be back at work until Tuesday, so you'll have to hold on until then for me to get it to you.

Contact me to contract me for D7 -> D10/11 migrations.

bradleyvance’s picture

If I put the 'die' command right after

$tabs = $vars['tabs'];

I got the blank white screen. But if I put it after

$items = explode("\n", $tabs);

then I got the proper output like this:

Array
(
    [0] => 

    * View
    * [1] => Addresses
    * [2] => Bookmarks
    * [3] => Edit
    * [4] => Files
    * [5] => Orders
    * [6] => Track page visits
      [7] => 


)

Clearly we now have "view" showing up, so, I commented out the following lines, and it seemed to be working, .first was assigned to "view" as it should except now .last isn't getting assigned at all. I guess that makes sense though because from the output above there's an extra number at the end of the list (7).

function phptemplate_preprocess_page(&$vars)
{  
    $tabs = $vars['tabs'];
    $items = explode("\n", $tabs);
    //die('<pre>' . print_r($items, true) . '</pre>');
    //$opening_ul = array_shift($items);
    //array_pop($items);
    //if(strpos($items[count($items) - 1], '</ul>') !== FALSE)
    //{
    //    array_pop($items);
    //}
   
    // process the first item
    if(strpos($items[0], 'li class=') !== FALSE) // does it have a class yet?
    {
        $items[0] = str_replace('li class="', 'li class="first ', $items[0]);
    }
    else
    {
        $items[0] = str_replace('li', 'li class="first"', $items[0]);
    }

    // process the last item
    $lastindex = count($items) - 1;
    if(strpos($items[$lastindex], 'li class=') !== FALSE)
    {
        $items[$lastindex] = str_replace('li class="', 'li class="last ', $items[$lastindex]);
    }
    else
    {
        $items[$lastindex] = str_replace('li', 'li class="last"', $items[$lastindex]);
    }
    $items = implode(PHP_EOL, $items);
    // create a string from the array
    $vars['tabs'] = $opening_ul . PHP_EOL . $items . PHP_EOL . '</ul>';
}

So then I changed this:

    // process the last item
    $lastindex = count($items) - 1;

To this:

    // process the last item
    $lastindex = count($items) - 2;

And it properly assigned .last, but this seems like a hack to me and I imagine I'll probably run into issues down the road. Any ideas?

rambo2000’s picture

Add this to your template.php and replace 'mytheme' with the name of your theme


function mytheme_preprocess_page(&$vars)
{
    $tabs = explode("\n", $vars['tabs']);
	array_pop($tabs);
	end($tabs);
	$last_key = key($tabs);

	foreach ($tabs as $key => &$tab) {
		if (strpos($tab, 'li class=')) {
			if ($key == 0) {
				$tab = str_replace('li class="', 'li class="first ', $tab);
			}
			if ($key == $last_key) {
				$tab = str_replace('li class="', 'li class="last ', $tab) . '</ul>';
			}
		}
		elseif (strpos($tab, 'li ')) {
			if ($key == 0) {
				$tab = str_replace('li ', 'li class="first" ', $tab);
			}
			if ($key == $last_key) {
				$tab = str_replace('li ', 'li class="last" ', $tab) . '</ul>';
			}
		}
	}
    $vars['tabs'] = implode("\n", $tabs);
}

edit: it fails finding the last tab when using secondairy tabs

Alador77’s picture

Thanks this post was usefull for me, try on my $primary tabs and classes are added as we need.

Good piece of code here too : http://adaptivethemes.com/add-first-and-last-classes-to-secondary-local-...