How to add a tab to a node's display?
Hi, I posted this to the forums, but probably this is a better place to place it since it seems to be a problem for the advanced Drupal community...
I have a node type called XXXXX for example. When the node is displayed (by the author) usually there are two default tabs on the top:
View | Edit
Let's say I want to display some information related to the current node and I want to display that on a separate tab on the same level as View and Edit. So the tab line should look like this:
View | Edit | Related Information
The related information could be of any kind: static text, panels, blocks... ultimately I want to embed a view in this tab but the more general question is: How to add this tab for my node type XXXXX only?
Can you suggest some module(s) and settings, which allow for adding tabs without (with minimal) coding?
I know that Javascript Tools module provides tabs, also Panels module can be made to use those but all this is adding artificial visual tabs only. What I need is a real tab whit its own URL.
If a node's URL is http://mysite/node/111, much like the Edit tab loads in http://mysite/node/111/edit, I would like to add a new tab with an URL of type: http://mysite/node/111/new_tab
Thanks!


Tabs
I did something similar myself this week, and it took some hours to figure it out. Hopefully I have it roughly correct.
There's no non-coding way of doing this that I know of. I suggest making a new module (check drupal site for a starter module tutorial, if you haven't done one before. You'll just need the bare bones - a .module file and a .info file.)
I figured this out in part by adapting code from another module that uses tabs. By the way, could you let me know where you posted this in the forums, and I'll post this there too, see if people can't correct me where I'm going awry. This does work, but there may be an easier way, or problems with my interpretation of what Drupal is doing.
Three things to know before looking at the code:
Now, this is what I did. I wanted to add a 'help' tab to all profile account pages. I made a module and stuck in the following, (which I'd adapted from the subscriptions module - this also sticks a tab in people's profile. Always look for code to mimic!) (minus the php tags, which'll be in your module...)
The first part - admin/settings/useraccounthelp - just sets up an admin page for setting the URL the tab goes to. The settings function it calls is below, if you're interested. The key part is 'path' => "user/". arg(1) ."/".$helpurl. See here for what the arg function does. For your node problem, I guess you won't need to use the arg function coz you know what the node number is. I can't imagine you'll need to do the "if (arg" test either, though maybe you do - something like "if (arg(0) == 'taxonomy' && arg(2) == whatever your node type is.
It's "'type' => MENU_LOCAL_TASK" that makes the link a tab. Why not "'type' => TAB?" Christ only knows.
'callback' then tells it what function to call. I don't know if this is the quickest way of doing this, but my callback calls my send_to_help_page function, passing in the $helpurl. Then we get to the page I want via the drupalgoto function. Done.
Be interested to hear if that makes any sense and you can adapt it. Thought I'd better write since it took me so long to work out. I can't promise it's the best way of doing it, but it works for me. And don't forget to clear the cache!
<?php
/**
* Implementation of hook_menu().
<em>/
function accounthelptab_menu($may_cache) {
$items = array();
if ($may_cache) {
$items[] = array(
'path' => 'admin/settings/useraccounthelp',
'title' => t('User Account helppage settings'),
'description' => t('Choose the page that the user account help tab link goes to.'),
'callback' => 'drupal_get_form',
'callback arguments' => array('accounthelptab_admin_settings'),
'access' => user_access('administer site configuration')
);
}
//testing to make sure we're in a user profile...
if (arg(0) == 'user' && is_numeric(arg(1))) {
$helpurl = variable_get('accounthelptab_url', 'userhelp');
// User help page
$items[] = array(
'path' => "user/". arg(1) ."/".$helpurl,
'title' => t('Help'),
'weight' => 8,
'callback' => 'send_to_help_page', 'callback arguments' => $helpurl,
//'access' => user_access('maintain own subscriptions'),
'type' => MENU_LOCAL_TASK,
);
}
return $items;
}
function send_to_help_page($helpurl) {
drupal_goto($path = $helpurl);
}
/</em>*
* Define the settings form.
*/
function accounthelptab_admin_settings() {
$form['accounthelptab_url'] = array(
'#type' => 'textfield',
'#title' => t('URL for the account help page'),
'#description' => t('Enter the relative path for the user account help page.'),
'#default_value' => variable_get('accounthelptab_url', 'userhelp'),
'#title' => t('URL'),
);
return system_settings_form($form);
}
?>
thanks!
That sure helps me. You just saved me a solid day of newbie mucking about. Thanks a million!
Now I just need to generalize it. here goes nothing...
Thanks a lot! At this point
Thanks a lot! At this point I am not sure if I can use your example directly since I know Drupal only from administration/configuration point of view and I am a complete newbie to module development.
One of the key things I did not get is how to display the tab for a specific node-type only.
You made a comment that probably I won't need the arg(1) function but I think I do. I need that tab displayed for ALL nodes of a given type so the nid should be there.
Here is ultimately what I want to do:
I am using Node Hierarchy module to create a simple parent-child-like structure:
Destination 1
|- Picture1
|- Picture2
|- Picture3
Destination 2
|- Picture4
|- Picture5
|- Picture6
|- Picture7
etc.
So I have 2 node types: Destination and Picture (imagefield + imagecache) and I want to let my users add "pictures" to some "destinations".
Node Hierarchy has a default "Children" page, displayed as a tab in the parent node. Unfortunately that is hard-coded as an extension to the theme and it is simply a list-style page, which is not suitable for showing pictures.
Luckily it also provides views integration so I defined a view, that shows the child-pictures in a nice gallery-style page with thumbnails.
So my real problem is how to add that view to ALL nodes of type "destination"
I tried two similar approaches: (1) using the views only and (2) using Panels 2 (a panel with embedded view).
I prefer Panels 2 slightly because it has the ability to configure a panel in the context of a specific node type only while the view would require some custom coding to do that.
Using both methods I have defined a proper URL: node/arg$/pictures, which works fine for a given destination node. What remains is to somehow add that view or panel to the "destination" node type.
Both Views and Panels provide settings (checkboxes) called "Provide Menu", "Provide Menu as Tab" and "Make Default Menu Tab". No matter how I combine them I am not getting the desired tab. In Panels 2 I actually managed to display a tab in the Preview mode but not in the real node display. So I wonder what these options are for ;-)
Anyway, having read your posting (as far as I understand it), I guess the idea is to create a module that (let's make it more general => usable) will have a configuration page under the Drupal administration, that will allow the administrator to associate node types with some URLs (e.g. node/arg$/pictures) and for each such URL will display a tab in all nodes from the associated type.
I would like to try building such a module but I am missing the structure and really don't know there to begin ;-(
I hope that makes sense to you. Any comments or ideas regarding this problem are welcome!
Thanks!
I spent some more time
I spent some more time reading the code and I think I now have some understanding of it... and of course some questions ;-)
There are two main if {...} operators in the accounthelptab_menu function.
The second one is clear - it adds the tab we need to the menu system and sets the proper URL for its contens.
I assume that Drupal scans all modules for XXX_menu functions and that's how it is rendering the menus. Please correct me if I am wrong.
I don't fully understand the meaning of the code within the first if {...} operator. From the values added to the $items array, I see that this is probably creating a settings page under the Drupal administration. What I don't understand is why is this code inside the accounthelptab_menu function? What is it doing there?
I would assume that creating an administration form and creating a menu (tab), which reads from that form are two separate tasks. Please correct me if I am wrong.
I would also assume that I'll have to replace the line:
"if (arg(0) == 'user' && is_numeric(arg(1))) {"
...with something that checks if we are in the context of a node of type XXX. What would such a check look like?
Thanks a lot for your help!
I spent a few more hours and
I spent a few more hours and I think I did it!
I had to give my view the same path as the new menu/tab's path so that they did't mix up to much. Here is the code:
<?php
function dest_pics_menu($may_cache) {
$items = array();
if (arg(0) == 'node' && is_numeric(arg(1))) {
$node = node_load(arg(1));
if ($node->nid && $node->type=="destination") {
$items[]= array(
'path' => 'node/'. arg(1) .'/dest_pics',
'title' => t('Destination Pictures'),
'callback' => 'send_to_dest_pic_page',
'callback arguments' => array(arg(1)),
'type' => MENU_LOCAL_TASK,
'weight' => 10
);
}
}
return $items;
}
function send_to_dest_pic_page($parent_nid) {
drupal_goto($path = "node/". $parent_nid ."/dest_pics");
}
?>
One thing that I noticed in the sample code flying around was the if ($may_cache) {...} construct. I looked in the documentation and it literally says: "$may_cache A boolean indicating whether cacheable menu items should be returned."
What exactly is a "cacheable menu item" and who defines which menu is cacheable and which is not?
Anyway, now I have other walls to break (like how to configure a view to get its title from the parameter node ID...)
Thanks a lot for your help!
Panels
Is there anyway we could get this working with Panels? Panels Tabs, for example, does this sort of thing, except not within the View/Edit tab menu.
I got that working using
I got that working using Panes Tabs. The problem there is that the tabs there don't have their own URLs and are independent from the main menu/tabs system in Drupal. While that is not a big problem in itself, having tabs related to a single node in two lines (or groups) with different behavior looks like a bad interface design idea.
That is why I am looking for a single solution for that problem. If only the "Provide Menu as Tab" option is Panels 2 worked the way I thought it should work... ;-)
bug fixed
I think this bug got fixed in Panels Pages RC2. Should be able to actually see the tabs now instead of having them be invisible until you hit the url directly.
Dave
for Drupal 6 users...
for Drupal 6 users... seems you need to use %node instead of arg(1). Probably would help ive fully read the new menus for D6 first but hope that saves someone sometime.
$items['node/%node/new_tab'] = array(
...
)
but for only certain node types...?
<?php$items['node/%node/new_tab'] = array(
'title' => 'New Tab',
'page callback' => 'mycallback',
'page arguments' => array(1),
'access callback' => TRUE,
'type' => MENU_LOCAL_TASK
)
?>
will work for adding a tab to a node in D6. However, to make this apply to only certain node types is beyond me.
The D6 menu migration pages at http://drupal.org/node/103114 state that:
Non menu code that was placed in hook_menu !$may_cache so that it could be run during initialisation, should now be moved to hook_init.But what to do in hook_init escapes me. Does anyone have any idea what to do?
Ryan
loader function
best way to do this is with a loader function. easy peasy! See here: http://drupal.org/node/307510
make the change in the theme?
I have done this by making the change in the theme. Although probably not the best way to do it, I found it to be quite an easy workaround.
E.g. I wanted to add a "New" tab to enable the content poster to quickly add more content. I searched for a more elegant way of doing this first but couldn't find a solution so I modified my theme:
<?php$nodetype = $node->type;
$newnode = "<li ><a href=\"/node/add/$nodetype\">New</a></li>";
if ($tabs): print '<ul class="tabs primary">'. $tabs . $newnode . '</ul></div>'; endif;
?>
You could fairly easily apply this to specific node types by populating $nodetype only if $node->type was equal to a specific set.