Error message

Deprecated function: implode(): Passing glue string after array is deprecated. Swap the parameters in drupal_get_feeds() (line 394 of /home1/tylerfra/public_html/includes/common.inc).

Drupal - Create a Menu Tab with Views for a Node Content Type

Category: 

This article describes how to place a custom local task menu tab alongside the 'View' and 'Edit' tabs on a content type.

In the example screen shot above, we have various local task menu tabs when viewing a node as an admin user. The 'View', 'Edit' and 'Revisions' tabs are provided by Drupal Core, the 'Group' and 'Menus' tabs are provided by the Organic Groups module and the 'Devel' tab is provided by, you guessed it, the Devel module. The 'Content' tab is our custom tab. When clicking on it, we want the page to display all the nodes related to the current node through a node reference field.

Creating the local task menu tab...

Remember, user #1 always has access, and will always see the tab!

1. Create a View with a Page Display and Menu Tab

2. Set the Page Settings for the View Display

In this particular example, we want the custom tab to be called 'Content' and to have its path set to:

node/%/content

A path like this is ready to take in a Node ID Views Contextual Filter, so the results are filtered on the node we are looking at. It is important your path follow this pattern so it will fall into the correct local task menu.

Next we'll  setup the Menu Tab in the Page Settings for the View Display.

We also can limit access to certain user roles, if necessary.

3. Setup the View Fields, Sort Criteria and Filter Criteria

Feel free to add any fields, sorting and filters you'd like to get the data you'd like. In my case, I was only interested in sorting by the nodes last updated date and seeing these fields:

  • Node ID
  • Node Title
  • Node Type
  • Author
  • Created
  • Last Updated
  • Published

4. Setup the View Contextual Filter

In my particular example, I did not need any filters because I wanted results across all content types and didn't care if they were published or not. What I did want however, was a contextual filter so the results could be limited to the current node.

Under the 'Contextual Filters' settings on your View Display, click the 'Add' button to create a contextual filter for your Node ID. Typically you could use one of your node reference fields here as your contextual filter. You may have to tinker with your Contextual Filter settings for a while to get the results you want, be sure to use the 'Preview' mode in views and send along a Node ID to the preview.

5. Save the View

Once you have the results you want filtering on your node id, just save your view and you will now have a custom local task menu tab show up on all of your nodes!

Pretty cool huh? Yes indeed. But what if you want this tab to only show up on a particular content type? Because right now, it will show up on all content types.

How to get the tab to show up on a specific content type...

UPDATE: See this comment for a much easier approach to controlling access to this tab. Otherwise you may try the alternative code-based approach listed below. However, I don't think this approach works anymore with later versions of Views because I can't seem to get my Views path to show up in the $items array. So your best bet would be to use Joachim's comment linked above.

1. Implement hook_menu_alter() to set a custom 'access callback' function on our views' page path

/**
 * Implements hook_menu_alter().
 */
function my_module_menu_alter(&$items) {
  // Set a custom access callback function for our view page display path.
  $items['node/%/content']['access callback'] = 'my_module_node_content_custom_access_callback';
}

2. Implement hook_module_implements_alter() so our module's hook_menu_alter() is called after views

/**
 * Implements hook_module_implements_alter().
 */
function my_module_module_implements_alter(&$implementations, $hook) {
  // When the implementations of hook_menu_alter are called, we need our module
  // to be called after views, so let's remove it from the implementations then
  // add it to the end.
  if ($hook == 'menu_alter') {
    if (isset($implementations['my_module'])) {
      unset($implementations['my_module']);
      $implementations['my_module'] = FALSE;
    }
  }
}

3. Implement our custom 'access callback' function which will determine wether or not our custom menu tab will show up

/**
 * A custom 'access callback' function used by our view page display
 * to determine if its local task menu tab should show up or not.
 */
function my_module_node_content_custom_access_callback($options = array()) {

  // Grab the default access callback function name, prepare the access
  // arguments, then see what the default access call back result is
  // according to views.
  $access_callback = $options[0];
  $access_arguments = $options[1];
  $access = call_user_func_array($access_callback, $access_arguments);

  // If the default access call back was false, then the user is not allowed
  // access.
  if (!$access) {
    return FALSE;
  }

  // So far the user is allowed access from the views' settings, let's now
  // determine if we want to customize the access to the tab.

  // If the node type is not an article, then we'll deny access, otherwise grant
  // access. 
  $node = node_load(arg(1));
  if ($node && $node->type != 'article') {
    return FALSE;
  }
  else {
    return TRUE; 
  }
}

4. Flush all of Drupal's Caches

After implementing the two hooks and custom access callback function in your custom module, flush all of Drupal's caches then check out your new menu tab!

Conclusion

Thanks for stopping by and reading this article, I hope it helped bring a custom local task menu tab to your content type.

Please let me know of any alternative methods and/or contrib modules that would make this task easier!

Comments

Thanks for it nice article.

tyler's picture

No prob alinouman, thank you!

Hi Tyler,

Some time ago I did EXACTLY the same stuff as yours in my site, but in addition to filtering the "node type" where to show the Views tab, I had the need to hide the Views tab if the View returns no results.

To do so the only solution I found was to programmatically execute the Views inside my 'access callback' and then return FALSE if the views returns  no results. Obviously this is not a good workaround because every time a node is viewed I have to execute a view... but it is the only I found. (ref to: http://drupal.org/node/1485188 )

Do you know some alternatives?

Thank you

tyler's picture

Hi MXT,

The alternative that comes to mind, would be to use hook_node_load(), then when your node is loaded, set up the hook to check the view result count, then attach something like $node->show_my_view_tab = true;

Then in your access callback function (the node should be passed to it), check for that boolean value, then return your true/false value. I hope this helps!

I'm new to Drupal. Could you by any chance show the code for this?

Sir,
I am new to drupal. I want to, insert new tab into main menu. I have added. But I dont know where that page contents will added.Can you help me ?

Sir,
Already I have added blocks. I dont know where we will add New menu tab' s 'Body content'. Whenever I add new block, that time body content option is there.

Hi!

Thank you for this post! I tried it out, and it owrks fine. 

I have not used it, howeer, as I could not figure out how to make this work for more tabs and different content types..

 

Then I found out, that views already does all this out of the box in advanced->contextual filters->specify validation criteria

tyler's picture

Thanks Joachim! Next time I try placing a tab on a content type, I'll be sure to check out the 'specify validation criteria' on the view's contextual filter settings to see what options are available.

tyler's picture

Thanks again for this Joachim, just today I needed this feature again, and the approach you mentioned works much better. In fact, the hook_menu_alter() approach doesn't seem to work anymore, because the path I created in my view doesn't even show up in $items.

I've found out that if a view uses relations then the access callback url should be node/%views_arg/content instead of node/%/content. Otherwise EntityMalformedException may arise.

tyler's picture

Thanks Art!

Thank you! I've been messing with this for two hours and %views_arg was the key.

Every article which is about how to create tabs and sub-tabs using views 3 only explains with existing path. In this article it is node/%/ and some other articles its uasually admin/content.

How about doing in completely new path like example.com/content-list and below it subtabs like published/non-approved etc. Along with article I also followed http://www.tp1.ca/en/blog/views-and-drupal-7-creating-tabs-and-subtabs-2... but couldn't get it..

any help on this?

 

tyler's picture

"Sub tabs" are typically powered by local tasks. So I think you'd have to create a custom module with local tasks via hook_menu() to accomplish this. Otherwise, perhaps one of the many contrib modules that build menus can be of use here.

I'm trying to add an additional paramter in addition to filtering by content type but it's not working. Any suggestions?

global $user;

if ($node->type != 'event' && $node->uid != $user->uid) {

 return FALSE;

} else {

return TRUE;

}

When I put these two together it works:

  if ($node->uid != $GLOBALS['user']->uid) {    return FALSE;  }   if ($node->type != "event") {    return FALSE;  }

But when I try to add this 3rd paramter it doesn't:

  $group = $node->field_event_business['und'][0]['entity'];  $shared_users = $group->field_business_share_users['und'][0]['target_id'];  if ($shared_users && $shared_users != $GLOBALS['user']->uid) {    return FALSE;  } else {    return TRUE;  }