Drupal 6: Master/detail tabbed navigation

Master/detail navigation is a common scenario in graphical user interfaces for databases. After selecting a master record, the user is given the choice to view all detail records of a particular type that are in some way related to the master record. A natural design choice is to offer tabbed navigation to support this scenario. The first tab contains data from the master record, while subsequent tabs represent different types of associated detail records in tabular format.

The rest of this post explains how to implement the above scenario in Drupal 6. To be more concrete, let's assume that we have a CCK content type Tournament, which is associated by node reference with a CCK content type Result (a tournament contains multiple game results). We also have two views, Tournaments and Results, which list all nodes of the respective type using a table display and furthermore contain exposed filters for all fields - including the tournament title field. The intended final GUI is shown in the following two screenshots (click to enlarge):

Master tab screenshot Detail tab screenshot

The steps to implement the tabbed master/detail navigation are outlined below. Note that the solution does not use the official Views 2 API (which is still quite undocumented at the time of writing). It is based on trial-and-error experimentation - use at your own risk.

  1. Add a Tournament argument to the Results view definition. The argument should be configured with "Display all values if not present", and employ a validator for the node type Tournament. When set up correctly, the results of a particular tournament may be displayed by specifying the view's path followed by the tournament node ID. For example, invoking the view through path /results/12345 would only display results of tournament 12345.

    Note, however, that the Tournament filter and Tournament field, though redundant in the argument-based view, still appear. We will deal with them a bit later.

  2. Add a "local task" type menu item to create a tab for the Tournament node type. We limit the appearance of the tab to Tournament nodes by specifying a custom access callback.

    function bbo_scoring_hook_menu() {
      // ...
      $items['node/%node/bbo_scoring/results'] = array(
        'title' => t('Results'),
        'page callback' => 'bbo_scoring_tournament_results_page',
        'page arguments' => array(1),
        'access callback' => 'bbo_scoring_tournament_results_access',
        'access arguments' => array(1),
        'type' => MENU_LOCAL_TASK,
        'weight' => 100,
      // ...
    function bbo_scoring_tournament_results_access($node) {
      return $node->type == 'tournament';
  3. Implement the page function so that it outputs the embedded Results view. By the way, we hide the redundant exposed filter for tournament and the tournament field on this page. Regrettably, for some reason this trick does not work in view pre-hooks, which would seem a more universal solution.

    function bbo_scoring_tournament_results_page($node) {
      $view = views_get_view('results');
      $view->display['default']->display_options['filters']['field_tournament_nid']['exposed'] = FALSE;
      $display_id = 'page_1';
      if (!$view || !$view->access($display_id)) {
        return '';
      return $view->preview($display_id, array($node->nid));
  4. Alter the filter form's action to lead back to the tab page. Without this step, the default URL of the view would be displayed after applying the filter.

    function bbo_scoring_form_views_exposed_form_alter(&$form, $form_state) {
      if (arg(0) == 'node' && arg(2) == 'bbo_scoring' && arg(3) == 'results') {
        $form['#action'] = '/node/' . arg(1) . '/bbo_scoring/results';

1 comment:

vivan said...
This comment has been removed by a blog administrator.

Post a Comment