Improving navigability of Drupal taxonomy hierarchies

Drupal's taxonomy system is wonderful in many regards, but it has major FAIL issues. One of those is the navigation of vocabulary term pages where the vocabulary has a hierarchy. By default the taxonomy system uses pages at "example.com/taxonomy/term/%tid" to display teasers for nodes having the given term. Ideally these pages would enable browsing around in the hierarchy of terms in the given vocabulary, but that's not the case. Fortunately the fix is pretty simple though rather obscure.

The ideal in my mind is for the vocabulary term page to display these items:

  1. Term name
  2. Parent terms (with links to their vocabulary term pages)
  3. Child terms (if any, with links to their vocabulary term pages)
  4. Related terms (if any, with links to their vocabulary term pages)
  5. Term description

A design example to draw on are web directories like Yahoo's (see http://dir.yahoo.com/Computers_and_Internet/Internet/World_Wide_Web/). It has a breadcrumb trail showing the parent terms, an area showing links to related and child terms, and a list of items. Yahoo's directory may not have term descriptions but Drupal's taxonomy system does and it's a useful feature.

In Drupal one can enable breadcrumbs to display the parent vocabulary terms of a given vocabulary term. This might require the taxonomy breadcrumb module, I don't remember. Further the term description does display by default if the vocabulary term page is for a single term (see theme_taxonomy_term_page in taxonomy.module). What's missing are child terms and related terms.

The unfortunate bit is that taxonomy.module doesn't export a hook for this. Drupal uses hooks for almost everything but not one for determining content of vocabulary term pages. What's up with that? Another unfortunate bit is you can use either Panels or Views to override the default display for vocabulary term pages but neither provide a simple way to configure display of child and related terms. There are also modules that provide a different layout for displaying vocabularies, their terms, and the vocabulary term pages, and after using several I'm displeased with all of them. An additional complication is the significant amount of traffic going to existing vocabulary pages at the existing URL, and making that content be displayed elsewhere is bound to confuse the search engines and disrupt that flow of traffic.

There is a way that neatly slides into place without disruption. This method involves theming the vocabulary term page rather than any of the methods listed above. The default display of vocabulary term pages is controlled by theme_taxonomy_term_page in taxonomy.module. As a theme function it's therefore possible to override it in several ways. Searching around drupal.org I found someone else had already gone down the road of theming vocabulary term pages. This does of course mean installing support in your theme and if your site uses multiple themes you have to remember to apply this change to all the themes.

Simply add this code to the template.php in your theme. If a template.php doesn't exist then by all means add one. It's also possible to do this with a template file using names like "page-taxonomy.tpl.php", "page-taxonomy-term.tpl.php" or "page-taxonomy-term-nnn.tpl.php". The advantage of adding code to template.php is it's possible to duplicate the theme_taxonomy_term_page function and modify it to your liking.

/**
* Override theming of taxonomy pages
*/
function pixture_reloaded_taxonomy_term_page($tids, $result) {
  drupal_add_css(drupal_get_path('module', 'taxonomy') .'/taxonomy.css');

  $output = '';

  // Only display the description if we have a single term, to avoid clutter and confusion.
  if (count($tids) == 1) {

    $term = taxonomy_get_term($tids[0]);

    if ($children = taxonomy_get_children($tids[0])) {
      $output .= '<div class="subterms">';
      // $items = theme('directory_list_subterms', $children);
      foreach ($children as $child) {
        $items[] = l($child->name, "taxonomy/term/$child->tid");      }
      $title = t('Children of !term', array('!term' => $term->name));
      $output .= $title .': '. implode(', ', $items);
      $output .= '</div>';
    }

    $description = $term->description;

    // Check that a description is set.
    if (!empty($description)) {
      $output .= '<div class="taxonomy-term-description">';
      $output .= filter_xss_admin($description);
      $output .= '</div>';
    }
  }

  $output .= taxonomy_render_nodes($result);

  return $output;
}