Menu Local Tasks - Tutorial?
mcunliffe - February 24, 2007 - 15:58
I have read all the documentation I can find on hook_menu(). I understand the normal menu system fine, but somehow I just can't get it through my thick skull how to implement the local tasks.
Is there a good online tutorial that someone could point me to? Or could someone please explain this in simple terms from beginning to end. The regular menu system is very logical, I'm just not sure what I'm missing here. Every built-in module seems a little to complicated for me to understand.
- Martin

Brief explanation
2 simple steps to understanding local tasks.
1. DONT PANIC.
2. Steal the code you need from somewhere else ... like the user module for example.
Local tasks are displayed in the tabs across the top of the page. A good example of an implementation of local tasks is the login screen - by default you see 3 tabs login, register and forgot password.
The code for this is below
$items[] = array('path' => 'user/login', 'title' => t('log in'),'callback' => 'user_login', 'type' => MENU_DEFAULT_LOCAL_TASK);
$items[] = array('path' => 'user/register', 'title' => t('register'),
'callback' => 'user_register', 'access' => $user->uid == 0 && variable_get('user_register', 1), 'type' => MENU_LOCAL_TASK);
$items[] = array('path' => 'user/password', 'title' => t('request new password'),
'callback' => 'user_pass', 'access' => $user->uid == 0, 'type' => MENU_LOCAL_TASK);
So you can see that everything is exactly like defining a normal menu item, but just by setting the type to be MENU_LOCAL_TASK drupal will display the menu items as tabs.
Not sure if thats such a good explanation - but hopefully the code will paint a thousand words :)
cool cool,
Mike
Like books? Check out booktribes the new (Drupal based) community for book lovers
from Computerminds
Follow Up
Thanks for the input, it allowed me to make some great strides. A couple of follow up questions if I may...
(BTW, I have read and read and read the documentation, but I'm getting lost in it.)
1. When using the standard view/edit node screen [xxx_form()], is it possible to add another local task? It doesn't really appear to be working for me if it does.
2. If the above is not possible, I figure I can make up my own custom forms, etc. In this case then, how do I determine what action is being taken on the form. How do I know if it's a new record, an edit, or a deletion? This may sound a little naive but I'm also not sure where to put the code to execute the proper action.
Any help would be greatly appreciated.
- Martin
...
Yes, it's possible. You use the same code mdixoncm provided, but the trick is to find out the correct path. Let's say we want to add a 'bozo' tab besides the 'view' and 'edit' ones. Drupal knows tabs are siblings by examining their paths. So the 'bozo' tab must have a 'node/1234/bozo' path for Drupal to know it's a sibling to 'view' (node/1234) and 'edit' (node/1234/edit).
Here's how to do it:
function mymodule_menu($may_cache) {
$items = array();
if ($may_cache) {
// ...
} else {
$items[] = array(
'path' => 'node/'. arg(1). '/bozo',
'title' => t('Bozo this node'),
'callback' => 'mymodule_bozo',
'type' => MENU_LOCAL_TASK,
);
}
return $items;
}
function mymodule_bozo() {
return "Your node has been bozoed successfully!";
}
Note the
arg(1)thing. It extracts the node ID from the URL. If we're browsing node #234, our bozo 'path' will be 'node/234/bozo'.Web programming is event-based. It's not the old-style programming we learned at high-school, where we were the ones to hand control to each procedure (aka 'function') ourselves. Event-based programming is somewhat different.
And we don't program the application from scratch. We use some framework. Drupal is such a framework. There are others as well.
Don't fret when you don't understand code you see. You're not supposed to understand it as long as you haven't learned the basics of the Drupal framework.
let's return to my code above. When I handed the
$itemsarray to Drupal, I described to him what piece of code it should hand control to when the 'node/123/bozo' path is accessed. That's event-based programming. It's different from traditional programming.Start at the beginning. the handbooks contain some tutorials.
Thank you.
Thank you moofie.
This has provided that last little bit of understanding that I needed. After your additional notes and some more reading I've got exactly what I needed now. Thank you for all your time.
- Martin
I am not able to add local tasks
Hi,
I read your reply I follow the same instructions. Still local tasks are not displayed.
code in mymodule_menu
$items[] = array(
'path' => 'node/add/edit',
'title' => t('Bozo this node'),
'callback' => 'mymodule_bozo',
'type' => MENU_IS_LOCAL_TASK,
);
I check in menu.inc following condition is not returning true.
menu.inc
if (($_menu['items'][$mid]['type'] & MENU_IS_LOCAL_TASK) && _menu_item_is_accessible($mid)) {
Can u please help me
...
What are you trying to achieve here?
'node/add/edit' seems weird to me.
It is eg
I want to give path for different modules like amazon search
Yeah but why would you want
Yeah but why would you want to ADD and EDIT content at the same time ? It doesn't make sense.
Caroline
A coder's guide to file download in Drupal
Who am I | Where are we
11 heavens
Adding tabs
I'm not sure if this applies or will help anyone... but I was looking for a way to just add items to the local tasks tabs and only for specific nodes. I added this to my page.tpl.php file before the $tabs variable is called.
if ( !empty($tabs) && $_GET['q'] == 'node/' ) : $tabs = ereg_replace("</ul>", '<li><a href="?q=node/" target="_blank">Tab Title</a></li></ul>', $tabs); endif;You can use this for as many items as you want and also have it apply to all pages by removing
&& $_GET['q'] == 'node/'in the if statement
Note entirely sure that this is the best way
Adding functional stuff like this to the template it generally considered bad practice... A better method would be to create a module and in hook_menu do...
<?php
function example_perm() {
return array('example callback permission');
}
function example_menu($may_cache) {
$items = array();
if ($may_cache) {
//Nothing goes in here
}
else {
//Non-cachable menu items
if (arg(0) == 'node' && is_numeric(arg(1)) {
$node = node_load(arg(1));
if ($node->type == 'blog') {
$items[] = array(
'path' => 'node/' . arg(1) . '/example',
'title' => t('Example Tab'),
'callback' => 'example_node_callback',
'access' => user_access('example callback permission'),
);
}
}
}
return $items;
}
function example_node_callback() {
return t('THIS IS AN EXAMPLE!');
}
?>
That example will check the path and, if there is a node on this path (eg node/5) then it will load the node (eg, 5) and then check its type (eg, blog). If it IS a 'blog' then it will add a new tab to the list and it will also handle the callback AND the access permissions (so if your user does not have a role with that permission set then you will NOT see the tab).
Just noticed (a few weeks
Just noticed (a few weeks later) that there needs to be a type => MENU_LOCAL_TASK on the example menu item.
VERY helpful...
VERY helpful... thanks.
There is one other minor typo... You need one more ")" at the end of your first conditional.
Here is the re-written code with the extra ) and the MENU_LOCAL_TASK edit implemented.
<?php
function example_perm() {
return array('example callback permission');
}
function example_menu($may_cache) {
$items = array();
if ($may_cache) {
//Nothing goes in here
}
else {
//Non-cachable menu items
if (arg(0) == 'node' && is_numeric(arg(1))) {
$node = node_load(arg(1));
if ($node->type == 'blog') {
$items[] = array(
'path' => 'node/' . arg(1) . '/example',
'title' => t('Example Tab'),
'type' => MENU_LOCAL_TASK,
'callback' => 'example_node_callback',
'access' => user_access('example callback permission'),
);
}
}
}
return $items;
}
function example_node_callback() {
return t('THIS IS AN EXAMPLE!');
}
?>