Contextual links on Views

Error message

The spam filter installed on this site is currently unavailable. Per site policy, we are unable to accept new submissions until that problem is resolved. Please try resubmitting the form in a couple of minutes.

Contextual links is a great addition for the admin UI of any website. So much so that it has become part of the core in Drupal 7.0. It adds administrative links on nodes, comments, views... But it doesn't cover absolutely everything. What if you want contextual links for the nodes inside a view? What about views rows? That depends on your rows. It works out of the box when using the "Node" row plugin, because of the native node contextual links. If you're using field rows though, you'll have to do some hacking to get it there.

The contextual links

The API for contextual links is quite solid and flexible, so that you can create new links for other entities and such, but what's important here is to understand how they're loaded and rendered within contexts. There are 2 basic functionalities for it: contextual_get_links() function and theme_contextual() theme function. contextual_get_links() takes 2 arguments: the type of the entity (node, comment, etc...) and the object itself. So basically, we can use these 2 to make the links available anywhere we have a node, which is what we want to do.

Getting it working through theming

One easy solution to this problem is to do everything inside the theme. First, we fetch the links and render theme by implementing a preprocess hook on your theme's template.php:

<?php
/**
 * Implementation of hook_preprocess_views_view_unformatted()
 */
function mytheme_preprocess_views_view_unformatted(&$vars) {
 
$view = $vars['view'];
  foreach (
$view->result as $id => $item) {
    if (isset(
$item->nid)) {
     
$vars['contextual_node'][$id] = theme('contextual', contextual_get_links('node', node_load($item->nid)));
    }
  }
}
?>

This is a very simple snippet that will preprocess the links on all views. This doesn't necessarily render the links in the view output, only makes them available to the template. You can leave as it is or use if ($view->name == "my_view" && $view->current_display = "page_1") {} to select a single view only. This can be critical for performance if you have many views on the same page.

Once the contextual links are available to the template, you just have to implement the template for views_view_unformatted (if you have a hard time with the views name conventions, you can get a cheat sheet by clicking on "theme" in the views UI). After properly choosing the file name for your template, copy the contents and add the line that prints our new variable. This sounds a lot more complicated than it is. Here is an example:

<?php
/**
 * @file views-view-unformatted.tpl.php
 * Default simple view template to display a list of rows.
 *
 * @ingroup views_templates
 */
?>

<?php if (!empty($title)): ?>
  <h3><?php print $title; ?></h3>
<?php endif; ?>
<?php foreach ($rows as $id => $row): ?>
  <div class="<?php print $classes[$id]; ?>">
    <?php print $row; ?>
    <?php if ($contextual_node) print $contextual_node[$id]; ?>
  </div>
<?php endforeach; ?>

That's it! All that is left is to properly style it. You have to add a {position: relative} to the view rows, as it's required for the links to be properly positioned.

Another approach

There are probably other approaches out there. One interesting possibility is implemented in a patch here: http://drupal.org/node/1096052 . Maybe it'll be integrated in the code later. Unfortunately, the work on this issue is kind of slow. But the patch should work well. If you care to, please help by testing and improving it.

Until then, have fun using contextual links in views!