Theming In Drupal 6

Photo of Greg Harvey
Fri, 2008-11-28 18:07By greg

Don't get me wrong, the documentation relating to theming for module developers in Drupal 6.x is all there and complete. The trouble is, it's spread over several different places. The purpose of this blog entry is to do a step-by-step guide to theming something in a module for Drupal 6. It is inspired by my colleague, Brendan, who created a really neat little demo module this morning which shows how the various bits of the theming API work. I felt, having compiled all the information in one place, it deserved a write-up.

Let's start by casting our minds back to Drupal 5.x. All good modules (be ashamed, Tracker!) output any mark-up through special functions called theme functions. Why are they special? Because they can be invoked using the Drupal theme() function, which does some clever stuff. It allows theme developers to over-ride mark-up set by module developers. A typical theme function in a module might look something like this:

function theme_my_markup() {
$html = '

' . t('This is some HTML.') . '

';
return $html;
}
?>

And you might invoke it, as a menu callback, block content, etc. something like this:

theme('my_markup');
?>

What happens if you just call theme_my_markup() directly? Nothing much, in so far is it will work just the same and return the same $html variable. The difference comes when a theme developer wants to over-ride the theme function. You see theme() checks first for a themename_my_markup() function in template.php. If it finds one, it takes precedent. If not it looks, again in template.php, for a phptemplate_my_markup() function. If it finds nothing else, it uses the theme_my_markup() function in your module.

In this way, the theme() function allows theme developers to over-ride any mark-up you provide, in template.php, without having to do any nasty module hacks. Nice! =)

All this still works in Drupal 6.x, but there's more. The first thing you absolutely must have in your module, or no theme function will work, is an implementation of hook_theme() which would look something like this:

/**
* Implementation of hook_theme().
*/
function my_module_theme() {
return array (
'my_markup' => array(
'arguments' => array(),
),
);
}

/**
* And of course, our theme function...
*/
function theme_my_markup() {
$html = '

' . t('This is some HTML.') . '

';
return $html;
}

/**
* And finally, let's output it to a hook_menu() implementation
* so we can view the output
*/
function my_module_menu() {
$items = array();
$items['my-page'] = array(
'title' => t('My test page'),
'page callback' => 'theme',
'page arguments' => array('my_markup'),
'access arguments' => array('access content'),
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
?>

The hook is vitally important because it *registers* your theme function with the new theme registry system for Drupal 6. Without the hook, Drupal has no idea your theme function exists and will totally ignore it. Now if you clear the Drupal cache (see below) and we visit the page http://yoursite.com/my-page then we will see the output of our theme function.

So, the code above is the direct replacement for Drupal 5.x code in a Drupal 6.x module. You don't need to do anything else. Except there's something else you can do with Drupal 6 - something even cooler - templates!

With Drupal 6 you can provide a template file for any element of theming you may wish to do. The template is used wherever the Drupal theme() function is called, as long as a template file is defined in your hook_theme() implementation. So let's first rewrite our hook:

/**
* Implementation of hook_theme().
*/
function my_module_theme() {
return array (
'my_markup' => array(
'template' => 'my-markup',
'arguments' => array(),
),
);
}
?>

Note the underscores in the theme function name are replaced with hyphens when we create our template reference. Note also that, in theory, the template name does not have to match the theme function name, however, we have discovered through testing that if the template name is different from the theme function name then the theme may not recognise the template file when it is coped to the theme's template directory. I'm not sure if this is by design, but for safety's sake make sure the template file and the theme registration are the same in name.

Next job is to create our template file. In the module directory we need to create a file called my-markup.tpl.php. This will contain the HTML which our template will place in Drupal wherever it is invoked. Let's start with a very basic template file, something like this:

This is my template.

One question you might be asking yourself is "what if I provide a theme function *and* a template...?" Well, the answer is the template always takes precedent over the theme function, so the theme function will do nothing as long as there is a template element in your hook_theme() array for that theme function/template registration. The output of your hook_menu() will be taken from the template file.

Another question you might be asking is "how can I over-ride this template in my theme?" Simple! Same as templates (which did exist) in Drupal 5.x - you make a copy of the file from the module directory in to the templates directory of your theme. If it doesn't exist, create it. Like the modules and themes directories of Drupal, the templates directory of a theme is recursively searched for .tpl.php files by Drupal, so you may create any directory structure you wish within your theme's templates directory.

By way of an example, if Garland is your default theme you can copy my-markup.tpl.php from your module's directory in to /themes/garland/templates and change it so it reads like this:

This is my template provided by the theme.

Clear the caches (admin/settings/performance -> click the "Clear cached data" button) because the Drupal 6 theme registry is very heavily cached. Then refresh my-page and you should see the altered text, showing you the template file in your theme's directory is taking precedence over the one in your module directory. Once you've proven this to yourself, delete the tpl file in your theme and clear caches again to continue.

And passing dynamic data in to template files? The missing link here is the ability to make templates dynamic. This is achieved through preprocess functions. To pass variables to your template from your module, you need to call a preprocess function within it like this:

/**
* This is a preprocess function passing variables to a template
*/
function template_preprocess_my_markup(&$vars) {
global $user;
$vars['your_name'] = $user->name;
}
?>

Notice the $vars is *referenced* when declaring the function, and there is no return value. The above function makes the $your_name variable available to my-markup.tpl.php containing the text 'This is a variable!'. So if I change my template file in my module to read like this, I'll actually see my variable outputted on my-page:

This is my template provided by the theme.

Your username is: .


And over-riding these? You probably won't be surprised to hear you can over-ride and/or add to these variables in template.php, or indeed in a different module. See the above preprocess link for the options available, but a quick example for the Garland template might be:

/**
* This is a preprocess function in the Garland theme's
* template.php file
*/
function garland_preprocess_my_markup(&$vars) {
global $user;
$vars['your_uid'] = $user->uid;
}
?>

Copy your tpl file to your theme templates directory again and change it so it looks like this:

This is my template provided by the theme.

Your username is: .


Your user ID is: .


Clear the caches and you should see your preprocess in template.php is adding your $your_uid variable is being added to your template and your tpl file in your theme is over-riding the one in the module.

And that is a brief overview of theming in Drupal 6. To bullet list the steps for the template approach, for reference:

  • Create a tpl.php file for your template
  • Create a hook_theme() implementation to register your template
  • Create a module preprocess function for any variables you wish to make present in the template
  • Clear caches
  • Go to the block/menu callback/other location you have invoked the theme() function from to call your template