Drupal Blocks and Contextual Menus

Submitted by Kevin on Wed, 09/02/2015 - 18:40

All blog entries reflect the opinions of the author and have not been expressly endorsed by the Ivan Allen College of Liberal Arts or the Georgia Institute of Technology.

Time for a tech-heavy post about making Contextual Menus work even better for blocks generated by a custom module.

Why does this matter?  If your block is displaying information pulled from elsewhere in your Drupal ecosystem, it's very helpful if your site's content manager can easily get to the content that drives that block.  Otherwise, it becomes the guessing game of "Where did this content come from?", especially when months or years have passed since the module was built.

The real beauty of this technique comes when you have a number of custom module-generated blocks on a landing page, and you can then hover over that contextual menu on any of them and jump right to the data source. Suddenly, maintaining the data on that page has become ten times easier than it was before.

Example #1: Editing a Node that is the Data Source for a Block

Let's say you've written a custom module to take data entered into a specific node and do something fancy with it before rendering it as a block on your site's front page. Adding a contextual link to allow editing that specific source node is downright simple:

function xxx_block_view($delta='') {
  global $language;

  $block = array();

  $block['subject'] = t('Your Block Title Here');
  $block['content'] = array(
    '#markup' => _xxx_generateBlockContent($delta),
    '#contextual_links' => array('xxx' => array('node', array(123)))),
    )
  );

  return $block;
}

In this example, your block will render with an 'Edit' link for node number 123. Unfortunately, it will also render with a 'Delete' link, as nodes provide both links for contextual menus. However, if you are using a custom content type then you can use permissions to prevent your content editors from being able to delete nodes of that type.

Example #2: Adding Contextual Links for Custom Functions

What if you want to link to something other than a node? Well, that gets a little more complex, but still perfectly feasible:


function xxx_menu() {

  $items = array();

  $items['zzz'] = array(
    'title' => 'Featured Projects',
    'description' => 'Featured Projects',
    'page callback' => 'xxx_generateLandingPage',
    'access arguments' => array('access content'),
  );

  $items['zzz/list'] = array(
    'title' => 'Manage Items',
    'context' => MENU_CONTEXT_INLINE,
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );

  return $items;

}

function xxx_block_view($delta='') {
  global $language;

  $block = array();

  $block['subject'] = t('Your Block Title Here');
  $block['content'] = array(
    '#markup' => _xxx_generateBlockContent($delta),
    '#contextual_links' => array('xxx' => array('zzz', array()))),
    )
  );

  return $block;
}

So, what's going on here? In a nutshell, contextual links have to be provided by a node in Drupal's menu system, so we can't just whip them up out of thin air. In this example, you want a 'Manage Items' contextual link to take the user to specially generated page 'zzz' in your Drupal site. To do that, you have to have both 'zzz' defined in the menu system, and a special contextual menu item, which in the example above is 'zzz/list' (you can make this whatever you like, BTW, and you can add as many additional items of this form as you need for your purposes. Those special 'context' and 'type' parameters tell Drupal that this is a contextual link and not a normal page in the menu system.

In case you're wondering about that '#contextual_links' parameter in the hook_block_view function, it takes a hash array where each key is the name of the current module, and the value for a key is an array of two parameters. The first is the menu path for the parent object whose contextual links you want to display, and the second is an array of parameters to pass to the function that handles rendering the page connected to that menu path. Note that as mentioned regarding the 'node' object above, you can't pick and choose which contextual links will be displayed for an object – Drupal automatically displays all defined contextual links for which the current user has permission to access.

Example #3: Linking the User to an Outside Data Source

If you want to redirect the user to an outside system, it's a little awkward to do with a contextual link, but possible. That looks something like this:


function xxx_menu() {

  $items = array();

  $items['zzz'] = array(
    'title' => 'Featured Projects',
    'description' => 'Featured Projects',
    'page callback' => 'xxx_generateLandingPage',
    'access arguments' => array('access content'),
  );

  $items['zzz/goRemote'] = array(
    'title' => 'Go to Remote Site',
    'page callback' => 'xxx_goRemoteURL',
    'context' => MENU_CONTEXT_INLINE,
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );

  return $items;

}

function xxx_goRemoteURL() {

  header('Location: http://www.somewhereelse.gatech.edu/');
  exit;

}

function xxx_block_view($delta='') {
  global $language;

  $block = array();

  $block['subject'] = t('Your Block Title Here');
  $block['content'] = array(
    '#markup' => _xxx_generateBlockContent($delta),
    '#contextual_links' => array('xxx' => array('zzz', array()))),
    )
  );

  return $block;
}

This shows one other useful permutation of the link configuration: you can get a contextual link to run a page callback function different from the one that the parent object uses. This can be quite useful in keeping the code in each function as simplified as possible. That said, you still have to have something as the parent object, even if it doesn't do anything useful. As I said before, contextual links can't just be whipped up out of thin air.