Drupal 6 Theming for Module Developers - Suggesting TPLs From a Module

A common problem that Drupal module developers run into is: creating a module with an appealing default user-interface, but also allowing designers to easily override the default UI, in a theme. There are multiple ways to solve this problem. While Drupal theme architecture is certainly capable of allowing such implementations, the exact code you need to write depends on which kind of template you are providing default implementation for.

A common problem that Drupal module developers run into is: creating a module with an appealing default user-interface, but also allowing designers to easily override the default UI, in a theme. There are multiple ways to solve this problem. While Drupal theme architecture is certainly capable of allowing such implementations, the exact code you need to write depends on which kind of template you are providing default implementation for. Whether you are “suggesting” a views template, node template or custom template you may need to write code in different ways.

In all use-cases described below, you can create a tpl.php in your module and designers can customize it simply by copying the file to a default theme and editing it.

Suggesting a Custom Template

Often a module needs to render an HTML output. Rather than “echo”-out the output, a proper module would define a TPL, provide default implementation of the output and allow overrides from theme folders.

To define a theme-able output, you need to implement a hook_theme:

1
2
3
4
5
6
7
8
function twitter_pull_theme() {
  return array(
    'twitter_pull_listing' => array(
      'arguments' => array('tweets' => Null, 'twitkey' => Null, 'title' => Null),
      'template' => 'twitter-pull-listing',
    ),
  );
}

This definition allows you to create twitter-pull-listing.tpl.php in your module and expose three variables: $tweets, $twittkey and $title to it. Once you do this you can render the output, using the TPL, anywhere in your module with a call like:

1
print theme('twitter_pull_listing', $tweets, $twitkey, $title);

Please note that theme key uses underscore characters: “_” while the tpl name is defined with dashes “-”. This is a convention used by Views themes and we try to comply with it for consistency, even though it is not mandatory.

Suggesting a Node Template.

Another use-case is if you define a custom content-type in your module and want to provide default theming for it. This is different from the previous use-case because you don’t need to implement a hook_theme: node module already defines theme hooks for all registered node types. We do, however, need to let Theme Registry know that it needs to look in our module for a tpl implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Implementation of hook_registry_alter
*/
function op_author_theme_registry_alter(&$theme_registry) {
 
  //— Provide default node template implementations from this module,
  //— but make sure we give a correspodning tpl in a theme folder a chance 
  //— to override us.
  $idx = array_search('modules/node', $theme_registry['node']['theme paths']);
 
  if ($idx !== False) {
    array_splice( $theme_registry['node']['theme paths'],
                  $idx+1, 0,
                  drupal_get_path('module', 'op_author') . '/theme');
  }
               
}

Line 14 tells Theme Registry to also look in the “theme” sub-folder of the op_author module, when searching for tpl files. By default, Theme Registry would only look in the folders under the current theme (well, and its parent themes). This allows our module to provide default TPL implementations to any pre-defined theme (including a tpl for our node type).

Obviously, this technique can also be used for most cases when another module defines a theme.

Suggesting a Views Template.

To suggest a Views template (display-level, row-level or field-level) you could use theme_registry_alter() implementation, as described in the previous example, but it’s not the best way. In fact, you should not use it. Views adds quite some logic on top of bare-bones Drupal theming, when implementing its theming layer and you would have to add quite a number of hacky code lines to the previous example to get things working. Hacky code is never good, and in case of Views there’s no reason to write any such thing. Views is extremely friendly to module developers and among many other things provides a very simple way of suggesting TPLs from module code. While we are at it, we will also show how to pre-process Views variables before they reach the tpl file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function op_author_layout_theme($existing) {  
  $themes = array();
 
  // — Row-level theme for stories
  $key = 'views_view_fields__author_articles';
  $tpl_path = 'themes/' . str_replace('_', '-', $key);
 
  $themes[$key] = array (
    'arguments' => array('view' => Null, 'options' => Null, 'row' => Null),
    'template' => $tpl_path,
    'original hook' => 'views_view_fields',
   
    // — According to Views Advanced Help, we either need to do this or make module weight > 10.
    'preprocess functions' => array(
          'template_preprocess',
          'template_preprocess_views_view_fields',
          'op_author_layout_preprocess_views_view_fields',
    ),
  );
}

As you can see line 6 tells Views to look for a default implementation of views-view-fields—author_articles.tpl.php in the module’s “themes” sub-folder (we like to keep tpls in a separate folder). This implementation can be easily overriden by theme developers, by simply creating the same tpl anywhere in a theme folder.

Line 17 instructs Views to pre-process variables using a custom pre-processor (e.g. if you need to add some logic to the variables fetched from the DB before they are sent to theme for rendering). The signature of the pre-processor function, in our case is:

1
function op_author_layout_preprocess_views_view_fields(&$variables)

This example code is for row-level themes. For themes of other levels (e.g. style or display) you need to make sure to pass appropriate ‘arguments’ array:

1
2
3
display: array('view' => Null),
style: array('view' => Null, 'options' => Null, 'rows' => Null, 'title' => Null),
row style: array('view' => Null, 'options' => Null, 'row' => Null),

Again, make sure to use the right arguments line or you will get errors.

Defining a Custom Panels3 Layout From a Module

First you need to define a CTools plugins directory:

1
2
3
4
5
function op_author_layout_ctools_plugin_directory($module, $plugin) {
  if ($module  'page_manager' || $module  'panels') {
    return 'plugins/' . $plugin;
  }
}

Then you need to create: plugins/layouts/author_page_layout folder under your module’s folder. In that folder, you need to place:
1. author_page_layout.css – CSS file for the layout
2. author_page_layout.inc – .inc file defining the structure of your layout with a hook implementation that looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function op_author_layout_author_page_layout_panels_layouts() {
  $items['author_page_layout'] = array(
    'title' => t('Author Page Layout'),
    'icon' => 'author_page_layout.png',
    'theme' => 'op_author_layout_author_page_layout',
    'css' => 'author_page_layout.css',
    'panels' => array(
      'author_brief' => t('Author Brief'),
      'left_column' => t('Left Column'),
      'right_column' => t('Right Column'),
    ),
  );
  return $items;
#125;

3. author_page_layout.png – screenshot of the layout
4. op-author-layout-author-page-layout.tpl.php – tpl file of the layout. In case of op_author module it looks like:

<div class="clearfix" <span style="color: #000000"><span style="color: #0000BB"><?php </span><span style="color: #007700">if (!empty(</span><span style="color: #0000BB">$css_id</span><span style="color: #007700">)) { print </span><span style="color: #DD0000">"id="$css_id""</span><span style="color: #007700">; } </span><span style="color: #0000BB">?></span></span>>  <br><br>  <div class="header clearfix">  <br>    <h1><span style="color: #000000"><span style="color: #0000BB"><?php </span><span style="color: #007700">print </span><span style="color: #0000BB">t</span><span style="color: #007700">(</span><span style="color: #DD0000">'Author Bios'</span><span style="color: #007700">); </span><span style="color: #0000BB">?></span></span></h1><br>  </div><!—/header—><br><br>  <div class="top-featured clearfix"><br>    <div class="inside"><span style="color: #000000"><span style="color: #0000BB"><?php </span><span style="color: #007700">print </span><span style="color: #0000BB">$content</span><span style="color: #007700">[</span><span style="color: #DD0000">'author_brief'</span><span style="color: #007700">]; </span><span style="color: #0000BB">?></span></span></div><br>  </div><br>  <br>  <div class="clearfix"><br>    <div class="inside inner-column-left" ><span style="color: #000000"><span style="color: #0000BB"><?php </span><span style="color: #007700">print </span><span style="color: #0000BB">$content</span><span style="color: #007700">[</span><span style="color: #DD0000">'left_column'</span><span style="color: #007700">]; </span><span style="color: #0000BB">?></span></span></div> <br>    <div class="inside inner-column-right"><span style="color: #000000"><span style="color: #0000BB"><?php </span><span style="color: #007700">print </span><span style="color: #0000BB">$content</span><span style="color: #007700">[</span><span style="color: #DD0000">'right_column'</span><span style="color: #007700">]; </span><span style="color: #0000BB">?></span></span></div><br>  </div><!—/ .clearfix —><br><br></div>

Summary

Making Drupal modules designer-friendly, by implementing output with proper, overridable TPLs, is the best way to make Drupal more appealing to the designer community and allow them make Drupal prettier, in return. As we demonstrated in this quick blog-post, Drupal provides a lot of infrastructure for making advanced templating possible and easy, so there’s little excuse for not doing so.

Irakli Nadareishvili