See also: Theme handbook (D6)

Modules in Drupal have the capability to allow presentation to be overridden for use on individual sites; this capability is called theming. In order for the theme layer to be usable, a module must be written to take advantage of it. To do this, logic must be separated as much as possible from presentation.

To accomplish this, modules do as much of the work on the data as possible, and hand that data off to the presentation layer. Modules then provide default implementations that provide the basic presentation and serve as a basis for themes that wish to provide an alternate presentation. This is handled through the theme() function. Every chunk of output that is themed through the theme() function is called a theme hook. There are two ways to provide a default implementation. The easier but less recommended way is to provide a function, and the recommended way is to provide a template and a corresponding preprocessor function. We'll expand on this in a little bit.

Ideally, your module won't produce a single line of HTML output that is not in a theme implementation. In reality, administrative pages and very small items may not necessarily be processed through a theme function [theme()], but a sign of a well written module includes easily utilized themes that process through the theme() function.

Registering theme hooks

In order to utilize a theme hook, your module first has to register that this exists. This is a change starting in Drupal 6; this registration is necessary due to the amount of time automatic discovery of alternate implementations takes; by registering, this discovery can be done only when needed, and then is always available. In the end, the theming layer is actually just a little bit faster, even though it is now doing more work.

Your module's hook_theme (see the hook API documentation for details on hooks) will return a list of all theme hooks that your module implements. A simple hook_theme implementation might look like this:

function forum_theme() {
  return array(
    'forums' => array(
      'template' => 'forums',
      'arguments' => array('forums' => NULL, 'topics' => NULL, 'parents' => NULL, 'tid' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL),
    ),
  );
}

This registration tells us that a theme hook named forums is implemented. The default implementation is a template. Because there are different kinds of template engines, this registration does not include the extension used by the engine, though Drupal core only supports PHPTemplate templates for modules. These template files have the extension '.tpl.php'.

It also tells us that the forums theme function (template_preprocess_forums) takes 6 arguments, and they all default to NULL. (All arguments must be given defaults as we have no way to assure that a theme() call will provide the proper information. If in doubt, make the default NULL). These arguments are translated into the named variables for the template. When calling this theme hook, an author might write:

  $output = theme('forums', $forums, $topics, $parents, 17, 'ASC', 25);

If the 'template' had been left off, the theme hook's default implementation would be assumed to be a function named 'theme_forums'.

There are more options that can be set here, but these two are by far the most important. For more information, see the hook_theme documentation.

Implementing default templates

When implemented as a template, the .tpl.php file is required. It should be in the same directory as the .module file (though the 'path' directive can be used to place these templates in another directory or a sub-directory).

Templates should be as much pure HTML as possible, but there are a few functions that are explicitly encouraged in your templates. First is the t() function. Modules should always provide proper translatability, and templates are no exception. Themers need to have the direct text to work with, and translators need all of the text to be passed through t(). Therefore, the use of t() is encouraged in templates.

Another function that is encouraged in templates is format_date(). Since this function is, really, a presentation function, the presentation layer is the appropriate place for it. However, its use is somewhat arcane and difficult for people who are not familiar with PHP to use. Nonetheless, it should be used in templates.

For other functions, consider whether or not they really are needed at the presentation layer. If they are not, they can be used in the preprocessor layer. All templates may have an optional preprocess function, named template_preprocess_HOOK. For example, for our forums theme hook above, its preprocess function will be named template_preprocess_forums. (Look it up in the API. It exists).

The purpose of the preprocess function is to perform any logic that needs to be performed to make data presentable, and to sanitize any data so that it is safe to output. It is critically important that your output be secure and not contain XSS (Cross Site Scripting) vulnerabilities. And since data that is output often comes from users, this data must be sanitized before it is output. Since we assume that themers are not necessarily developers, we must assume that they are not going to fully understand how to do this; but that's ok, because we can sanitize data in the preprocess function by running it through check_plain, check_markup, filter_xss_admin or other output sanitizing functions.

Here is a simple example:

function template_preprocess_poll_bar(&$variables) {
  if ($variables['block']) {
    $variables['template_files'][] = 'poll-bar-block';
  }
  $variables['title'] = check_plain($variables['title']);
  $variables['percentage'] = round($variables['votes'] * 100 / max($variables['total_votes'], 1));
} 

First, note that the preprocessor function takes a reference to an array of variables. This array will be seeded with the arguments that were sent via the theme() and named by the 'arguments' section of the hook registration. Since this is a reference, simply modifying this array is enough to transport those changes to the template that accompanies it.

This example illustrates three important concepts:

  1. The 'title' field is unsafe, because it comes from user input. It is run through check_plain so that the template may safely output it.
  2. The theme hook receives the total number of votes and the number of votes for just that item, but the template wants to display a percentage. That kind of work shouldn't be done in a template; instead, the math is performed here. The variables still exist, though; a themer overriding this could easily choose to display something other than a percentage.
  3. The special variable 'template_files' can be used to provide alternative templates to use. This is an array of template files, and it is last in, first out, which means the last item added to the array will be the first one used. If that template doesn't exist, it will use the next one in the list. This method can be quite useful to use an array of different templates, but be warned that it is limited to just templates.

Theme Developer module, which is part of the devel project, includes a template log feature which outputs at bottom of page all the template files which could have been used to theme the current page. This may be handy while building your module, but even more so when themeing a site. Also, don't miss its Themer information popup.

Quick note: Template files should be named with hyphens instead of underscores. If the theme hook is 'forum_list', the template file should be named 'forum-list.tpl.php'.

Implementing theme functions

Drupal allows you to use functions for your default theme implementations. This is somewhat faster, as there is no check for alternative templates (since there aren't any).

The downside is that there isn't a preprocess function. This makes it harder for themers to override your theme function, unless they're developers. This is especially true if your theme function affects program logic rather than presentation. In general, using templates minimizes this risk.

Theme functions are named by prepending 'theme_' to the name of the hook. The arguments given to theme('hook') will be passed straight through, unaltered. The defaults specified in the hook registration will not be provided here; they must be provided as normal PHP argument defaults.

function theme_forums($topics = NULL, $parents = NULL, $tid = NULL, $sortby = NULL, $forum_per_page = NULL) {
  ...
  return $output;
}

Dynamic theming

In addition to being able to specify alternate templates in a preprocess function, you may also create dynamic theming implementations using wildcards. There are two steps in this process.

First, in hook_theme, you can specify a pattern. Patterns are simple regular expressions. ^ (beginning of line) is assumed, but $ (end of line) is not. To signify the dynamic portion of the pattern, a double underscore is the general convention; this is not required but it is highly recommended.

Second, when calling the theme() function, instead of a string for the first argument you may pass an array. This array is much like template_files above, but this one is first in, first out so the first one seen will be used.

For a practical example, the module Views likes to let each view be themed by name. Upon registration, the hook 'views_view' would register with the pattern 'views_view__'. When theming that view, Views would call:

  $output = theme(array("views_view__$view->name", 'views_view'), $view);

Views will implement a default view for views_view; if a theme registers 'views_view__foo' and Views themes a view named 'foo', the specific override will activate and be used instead. Unlike the 'template_files' variable in the preprocessor function, this works for both theme functions as well as templates.

More on preprocess functions.

theme('table') and theme('item_list')

Drupal provides a few helpers to build complex HTML constructs easily. These are very useful features, and by using them it is easy to create a consistent look on tables and lists. The downside is that they are not readily accessible to a themer. Instead, they place code that should be at the presentation layer into the logic layer, and only advanced themers are able to do anything with it.

When creating output that is likely to be changed, it is best to avoid the use of these constructs and create the tables and lists with plain HTML. The forum themes are perfect examples of how to accomplish this and still create HTML that is consistent.

If you are implementing a custom theme function instead of implementing default templates, you will need to "restart" (disable and enable) your module after each modification in theme_custom() function.

Example here: http://drupal.org/node/226533

Also you can Clear Cache from admin/settings/performance or if you have Drush installed, with drush cc all

Comments

salexch’s picture

I tried to figure out how to make a field in my module to read from tpl.php file..

I will give two examples and will explain..

/**
 * Implementation of hook_nodeapi().
 */
function mymodule_nodeapi(&$node, $op, $teaser, $page) {
  switch ($op) {
	case 'view':
              $node->content['field1'] = array(
                '#value' => theme('mymodule_field1', 'field1 test argument'),
                '#weight' => 10,
              );
              $node->content['field2'] = array(
                '#value' => theme('mymodule_field2', 'field2 test argument'),
                '#weight' => 10,
              );
    break;
  }
}

/**
 * Implementation of hook_theme().
 */
function mymodule_theme() {
  return array(
    'mymodule_field1' => array(
         'arguments' => array(
               'data' => NULL,
          ),
     ),
	
    'mymodule_field2' => array(
	  'template' => 'field2_template', 
          'arguments' => array(
               'data' => NULL,
          ),
    ),
	
  );

}

function theme_mymodule_field1($data) {
	$output = $data;
	return $output;
}

Hope there is no mistake here..

now you need to create a file field2_template.tpl.php in your module folder mymodule/field2_template.tpl.php

inside of it
print_r($data);

now go admin/settings/performance and click on clear cached data

thats it..

first field aka field1 will be using theme_mymodule_field1 function for its teplate
the second one aka field2 will be using field2_template.tpl.php file for its template

hope Im right and it will help..

Marcelino’s picture

thanks a lot @salexch it really helped me :)