Is it possible to have contextual links on the title / headline of a node instead of printing the tabs (view and edit) underneath.

Comments

lsolesen’s picture

Anybody knows this?

extde’s picture

Contextual links for nodes configured by node_view()

  // Add contextual links for this node, except when the node is already being
  // displayed on its own page. Modules may alter this behavior (for example,
  // to restrict contextual links to certain view modes) by implementing
  // hook_node_view_alter().
  if (!empty($node->nid) && !($view_mode == 'full' && node_is_page($node))) {
    $build['#contextual_links']['node'] = array('node', array($node->nid));
  }

So, I just added to my theme

function THEME_node_view_alter(&$build) {
  $node = $build['#node'];
  if (!empty($node->nid)) {
    $build['#contextual_links']['node'] = array('node', array($node->nid));
  }
}

Not sure it is right to remove all limitations in node_view(), but it works.

Why it does not show contextual links for "except when the node is already being displayed on its own page"?

One issue is that not all links from tabs displayed in contextual menu.
For example "Devel" link is not displayed.
Looks like modules should have integration with this feature.
Modules should implement hook_menu_contextual_links_alter() to add contextual menu items.
There is also contextual links generated by menu_contextual_links()

Get all contextual links that are direct children of the router item and not of the tab type 'view'.

This implementation does not include links with MENU_CONTEXT_NONE and MENU_CONTEXT_PAGE.
Once again I do not understand why menu items with MENU_CONTEXT_PAGE excluded?

So, as a temporary solution, I also added hook_menu_contextual_links_alter() to my theme:

/*
 * Replacing existing items with items from menu_local_tasks()
 */
function THEME_menu_contextual_links_alter(&$links, $router_item, $root_path) {
  if ($root_path == 'node/%') {
    $local_tasks = menu_local_tasks();
    $local_links = array();
    foreach($local_tasks['tabs']['output'] as $item) {
      $link = $item['#link'];
      if ($link['title'] != 'View') {
        array_push($local_links, $link);
      }
    }
    array_splice($links, 0, count($links), $local_links);
  }
}

It is definitely hacking, but I can't wait for module developers.

If this will not work - make sure you have following line in your node.tpl.php or node--CONTENT_TYPE.tpl.php (this line already exists in core implementation, so, I am concerned about customized templates):

/*
 * This line will call your hook_menu_contextual_links_alter() finally
 */
print render($title_suffix); 
minff’s picture

All you have to do is put appropriate classes around the local tasks' (i.e. tabs') HTML and jQuery will do the rest.
I did it from my template's template.php:

function THEMENAME_menu_local_task($variables) {
  $link = $variables['element']['#link'];
  $link['localized_options']['html'] = TRUE;
  return '<li>'.l($link['title'], $link['href'], $link['localized_options']).'</li>'."\n";
}
function THEMENAME_menu_local_tasks($variables) {
  $output = '';
  if (!empty($variables['primary'])) {
    $variables['primary']['#prefix'] = '<div class="contextual-links-wrapper"><ul class="contextual-links">';
    $variables['primary']['#suffix'] = '</ul></div>';
    $output .= drupal_render($variables['primary']);
  }
  if (!empty($variables['secondary'])) {
    $variables['secondary']['#prefix'] = '<ul class="tabs secondary clearfix">';
    $variables['secondary']['#suffix'] = '</ul>';
    $output .= drupal_render($variables['secondary']);
  }
  return $output;
}

And then add to the content DIV (in your page.tpl.php file) a new class "contextual-links-region", then clear cache and voilà!

This code does it only for the primary tabs, though. But if you want, you can combine the primary and secondary tabs into one array and render it as one. I just didn't have a need for secondary tabs as for now.

codycraven’s picture

Very nice solution minff.

carn1x’s picture

Coolest thing I read all day :)

droath’s picture

Very simplified solution minff... thanks for sharing.

I do have a one modification to the code for the "THEMENAME_menu_local_tasks" function that I would like to share.

The additional code will display the default tabs if the current user doesn't have access to contextual links.

Additional code:

<?php
   $has_access = user_access('access contextual links');
    // Only display contextual links if the user has the correct permissions enabled.
    // Otherwise, the default primary tabs will be used.    
    $variables['primary']['#prefix'] = ($has_access) ? 
      '<div class="contextual-links-wrapper"><ul class="contextual-links">' : '<ul class="tabs primary">';
?>

Full code:

<?php
function THEMENAME_menu_local_tasks(&$variables) {
  $output = '';
  $has_access = user_access('access contextual links');
  if (!empty($variables['primary'])) {
    $variables['primary']['#prefix'] = '<h2 class="element-invisible">' . t('Primary tabs') . '</h2>'; 
    
    // Only display contextual links if the user has the correct permissions enabled.
    // Otherwise, the default primary tabs will be used.    
    $variables['primary']['#prefix'] = ($has_access) ? 
      '<div class="contextual-links-wrapper"><ul class="contextual-links">' : '<ul class="tabs primary">';

    $variables['primary']['#suffix'] = '</ul></div>';
    $output .= drupal_render($variables['primary']);
  }
  if (!empty($variables['secondary'])) {
    $variables['secondary']['#prefix'] = '<h2 class="element-invisible">' . t('Secondary tabs') . '</h2>';
    $variables['secondary']['#prefix'] = '<ul class="tabs secondary clearfix">';
    $variables['secondary']['#suffix'] = '</ul>';
    $output .= drupal_render($variables['secondary']);
  }
  return $output;
}
?>
lsolesen’s picture

The above code adds one div to much if the user does not have the correct permissions.

Change

 $variables['primary']['#suffix'] = '</ul></div>';

To

    $variables['primary']['#suffix'] = ($has_access) ?
      '</ul></div>' : '</ul>';
dgastudio’s picture

each of this examples, convert any tab to contextual links. But in my case, i only want contextual links on node view page.

i'll explain myself better. I use view to create tabbed navigation, so, using any of this snippets, the tabs are converted to contextual links.

so, how can i solve this problem? convert to contextual links only tabs on node view page?

minff’s picture

Actually there's one addition to be made: remove the "View" contextual link when displaying a node. Because, well, you don't need it when you're already viewing the node. One way to do this is to change THEMENAME_menu_local_task() into this:

function THEMENAME_menu_local_task($variables) {
  $link = $variables['element']['#link'];
  if ($link['path'] == 'node/%/view') return false;
  $link['localized_options']['html'] = TRUE;
  return '<li>'.l($link['title'], $link['href'], $link['localized_options']).'</li>'."\n";
}
lsolesen’s picture

I created a small article about this post. http://larsolesen.dk/node/317

Agence Web CoherActio’s picture

Very nice.

One complement for those using Omega theme.
You need the following function in the region preprocessor preprocess-region.inc in order to add the relevant class for contextual links.

function THEMENAME_alpha_preprocess_region(&$vars) {
  if (isset($vars['region']) && $vars['region'] == 'content'){
    $vars['content_attributes_array']['class'][] = 'contextual-links-region';
  }

Laurent

Open Social’s picture

This can also be interesting, now it works for every enity that has a page with tabs


function THEME_menu_local_task($variables) {
  $link = $variables['element']['#link'];
  $link['localized_options']['html'] = TRUE;

  $exploded = explode('/', $link['path']);
  if ($exploded[2] == arg(2)) return false;
  if ($exploded[2] == 'view' && arg(2) == FALSE) return false;

  return '<li>'.l($link['title'], $link['href'], $link['localized_options']).'</li>'."\n";
}
davidwhthomas’s picture

I found I didn't have the contextual links js available by default, so just got the unstyled links.

This version works for me, just the drupal_add_library line added

<?php
function THEMENAME_menu_local_tasks($variables) {

  // Add contextual links js and css library
  drupal_add_library('contextual', 'contextual-links');

  $output = '';
  if (!empty($variables['primary'])) {
    $variables['primary']['#prefix'] = '<div class="contextual-links-wrapper"><ul class="contextual-links">';
    $variables['primary']['#suffix'] = '</ul></div>';
    $output .= drupal_render($variables['primary']);
  }
  if (!empty($variables['secondary'])) {
    $variables['secondary']['#prefix'] = '<ul class="tabs secondary clearfix">';
    $variables['secondary']['#suffix'] = '</ul>';
    $output .= drupal_render($variables['secondary']);
  }
  return $output;
}
?>

thanks,

DT

CodigoVision’s picture

worked perfect for me, just what i was looking for!

isaacfreeman’s picture

Great thread.

I've taken an extra step in my theme and moved the contextual links from page.tpl.php into the #block-system-main block. I'm using a forked version of the Omega theme, with the following changes:

In preprocess-block.inc:

if ($vars['block_html_id'] == 'block-system-main') {
    $vars['classes_array'][] = 'contextual-links-region';
}

This adds the .contextual-links-region class to the block.

In block.tpl.php:

<?php 
  // If this is the main content block, render the tabs so that they can be displayed as contextual links.
  if ($block_html_id == 'block-system-main') {
    print render(menu_local_tabs());
  }
?>

This renders the tabs inside the block, using the code above.

With these additions, I was able to remove tabs from page.tpl.php entirely.

lsolesen’s picture

A good idea. Using panels and hiding the original title for the page - the h1 - with css to be able to decide for myself where the title is rendered the contextual links disappears when the contextual links are in page.tpl.php.

To make it easier to implement for other people, maybe both things can be achieved with a preprocess function?

lsolesen’s picture

That does still not solve my problem for panel pages. Another issue is that the title is not displayed in the block (for whatever reason that is) which means that the contextual links will not display exactly where it is expected.

cjsmith87’s picture

I am using skeletontheme and my content div has a class already defined:

<?php if ($page['sidebar_first']) { ?>
        <div id="content" class="eleven columns">
        <?php } else { ?>
        <div id="content" class="sixteen columns clearfix">
<?php } ?>

Im not an expert at this kind of thing but I am trying to learn. I can see that if I change it to class "contextual-links-region" it works but it messes up all the alignment of my theme. Just wondered if someone could explain how I could get round this?

cjsmith87’s picture

Pleasingly I solved it myself, disappointing it seems my html skills were to blame as I didn't know you could just add multiple classes with a space! Still, at least I learnt something!

OnkelTem’s picture

Announcing a Drupal 7 module which converts tabs to contextual links.
Module's name: TabsNoMore
URL: http://drupal.org/sandbox/onkeltem/1730244

ilfelice’s picture

TabsNoMore

Thank you for this! Works as expected out of the box. One little glitch:

The "View" tab content is displayed for user with no editing/admin rights. Any way to fix this?

OnkelTem’s picture

Thanks for reporting this.
I was not checking user access to "access contextual links" permission unconditionally printing links for all available tabs.
Now it is fixed:
http://drupalcode.org/sandbox/onkeltem/1730244.git/commit/7c79fde

Now if a user has no access to contextual links, tabsnomore_acitve() will return false and links would render as before.

ilfelice’s picture

Hi OnkelTem,

That worked!

Thank you so much for the quick response!

Jorge

lsolesen’s picture

Are you going to promote this to a full project soon?

OnkelTem’s picture

Aye!

ilfelice’s picture

Hi OnkelTem!

Now that I think of it, since the contextual menus always appear in the node view mode, the "View" tab should never be shown, regardless of the user role (anonymous or not).

Great module!

Jorge

Shawn Sparks’s picture

I find this to be a simple solution.


/**
 * Hide all primary tabs
 */
function THEMENAME_preprocess_page( &$variables ) {
	$variables['tabs']['#primary'] = array();
}

/**
 * Show contextual links on all nodes
 */
function THEMENAME_node_view_alter( &$build ) {
	$build['#contextual_links']['node'] = array('node', array($build['#node']->nid));
}

hobbes_VT’s picture

As outlined above in other comments, the above code is elegant simple, but still requires to you to add the class 'contextual-links-region' to the main content div in your page.tpl.php template to show up - as I've tried to replicate this for a different theme.