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

mdixoncm - February 24, 2007 - 17:12

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

mcunliffe - March 1, 2007 - 01:12

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

...

mooffie - March 1, 2007 - 04:55

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.

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'.

I'm also not sure where to put the code to execute the proper action

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.

I'm also not sure where to put the code to execute the proper action

let's return to my code above. When I handed the $items array 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.

mcunliffe - March 2, 2007 - 03:43

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

cpp - April 26, 2007 - 08:06

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

...

mooffie - April 26, 2007 - 12:59

'path' => 'node/add/edit',

What are you trying to achieve here?
'node/add/edit' seems weird to me.

It is eg

cpp - April 26, 2007 - 13:19

I want to give path for different modules like amazon search

Yeah but why would you want

Chill35 - April 30, 2007 - 02:22

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

justineggert - September 22, 2007 - 21:29

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

nicholasThompson - November 12, 2007 - 15:22

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

nicholasThompson - November 30, 2007 - 15:05

Just noticed (a few weeks later) that there needs to be a type => MENU_LOCAL_TASK on the example menu item.

VERY helpful...

tROCK - April 17, 2008 - 19:34

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!');
}
?>

I cannot figure this out to

ebeyrent - February 11, 2009 - 20:58

I cannot figure this out to save my life. When I tried to use this code, it definitely produced the local task as a tab. However, when I click on the tab, the callback does not get executed. Instead, the node reloads with node/nid/example in the url.

Any thoughts as to why this is happening?

Something similar

aangel - March 6, 2009 - 05:21

My code was calling the parent item too but for a different situation. Perhaps this will help someone.

In my case it was because on the second time through (i.e. after the user had clicked the local link), the test for whether the user was allowed to access the local menu failed. The test depended on a specific form (the form of the parent url) which was different from the child url, so of course it would fail.

With that failed test, the local menu wasn't inserted into the menu table. All Drupal would find is the parent, which it promptly displayed.

Recrafting the test so that it could handle the URL form of both the parent AND the local menu did the trick.

Andre'

A better way... for D6

heronog - May 8, 2009 - 21:48

All this techniques are very interesting, but checking the hook_menu documentations I think I came up with a way of creating local tasks depending on arbitrary conditions.

In my case, I was trying to add a local task to nodes of certain type, and noticed two very powerful properties of a menu entry, those are %placeholders and the 'access callback' property.

Using a custom 'access callback' function, we can create a menu entry that can be cached and that can be present on arbitrary conditions. Here's what I did:

function yourmodule_menu() {
 
    //notice the %node placeholder
    $items['node/%node/mylocaltask'] = array(
    'title' => t("Test local task"),
    'description' => t("Make a test for local tasks"),
    // THIS IS THE TRICK
    'access callback' => "yourmodule _check_local_tasks",
    // PRELOAD $node FOR US
    'access arguments' => array(1),

    // normal callback, just point to the function of your liking
    'page callback' => "carabas_send_test",
    // PRELOAD $node when executing the page callbac
    'page arguments' => array(1),

    // do this a local task
    'type' => MENU_LOCAL_TASK
  );
 
  return $items;
}

function yourmodule_yourlocaltaskcallback($node) {
// notice there is no "node_load" here, the $node is already loaded properly
  return "testing your node with id $node->nid and type $node->type";
}

function yourmodule_check_local_tasks($node) {
// same thing as above with $node
//change the test to your liking.
  return ($node->type == 'yournodetype') && user_access('your task permission');
}

Hope this helps!

--
Herón Ordoñez.

www.loquevienesiendo.com

 
 

Drupal is a registered trademark of Dries Buytaert.