Overriding Drupal 7 Theme Functions In Module Files

When overriding theme functions in Drupal 7 you would normally copy the theme function into the template.php and alter it to suit your needs. This isn't always convenient though, especially if you are trying to abstract functionality into modules and not have code in your theme layer that is reliant on module code. If you add theme override functions to your module files then they won't do anything as Drupal isn't looking for them there and won't pick them up.

It is possible to alter the theme registry in order to get Drupal to pick up your theme functions from your module code. This helps collect together code that performs a certain task and allows you to deploy theme alterations along with module updates.

To demonstrate how to do this I will run through how to override the theme function theme_date_popup(), which is used to print out the wrapper around the date popup form element (from the Date Popup module). I needed to do this recently in order to override one of the classes created in this function so it made sense to document things as I was doing it.

The theme_date_popup() theme function is kept in the file date_popup/date_popup.module in the Date module, and looks like the following.

/**
 * Format a date popup element.
 *
 * Use a class that will float date and time next to each other.
 */
function theme_date_popup($vars) {
  $element = $vars['element'];
  $attributes = !empty($element['#wrapper_attributes']) ? $element['#wrapper_attributes'] : array('class' => array());
  $attributes['class'][] = 'container-inline-date';
  // If there is no description, the floating date elements need some extra padding below them.
  $wrapper_attributes = array('class' => array('date-padding'));
  if (empty($element['date']['#description'])) {
    $wrapper_attributes['class'][] = 'clearfix';
  }
  // Add an wrapper to mimic the way a single value field works, for ease in using #states.
  if (isset($element['#children'])) {
    $element['#children'] = '<div id="' . $element['#id'] . '" ' . drupal_attributes($wrapper_attributes) .'>' . $element['#children'] . '</div>';
  }
  return '<div ' . drupal_attributes($attributes) .'>' . theme('form_element', $element) . '</div>';
}

In order to override the theme registry for this theme function we need to know how it is registered first. This can be found out by looking at the hook_theme() hook from the Date Popup module, which is used to register this theme function with Drupal. For the Date Popup module this is called date_popup_theme().

function date_popup_theme() {
  return array(
    'date_popup' => array('render element' => 'element'),
    );
}

All we need to do here is use the hook_theme_registry_alter() hook to our custom module in order to alter the theme registry. In this hook we look for the 'date_popup' theme registry entry and force any calls to the date_popup theme function to go through a function of our choosing. Here is the hook_theme_registry_alter() implementation in our custom module in full.

/**
 * Implements hook_theme_registry_alter().
 */
function custom_module_theme_registry_alter(&$theme_registry) {
  if (isset($theme_registry['date_popup'])) {
    $theme_registry['date_popup']['function'] = 'custom_module_theme_date_popup';
  }
}

We can now copy the theme_date_popup() theme function from the Date Popup module and override it. As long as the function name matches the custom function name we set in the hook_theme_registry_alter() it will be used. Here we are simply adding a custom class to the form field wrapper.

/**
 * Overrides theme_date_popup().
 *
 * Format a date popup element.
 *
 * Use a class that will float date and time next to each other.
 */
function custom_module_theme_date_popup($vars) {
  $element = $vars['element'];
  $attributes = !empty($element['#wrapper_attributes']) ? $element['#wrapper_attributes'] : array('class' => array());
  $attributes['class'][] = 'container-inline-date';
  // If there is no description, the floating date elements need some extra padding below them.
  $wrapper_attributes = array('class' => array('date-padding'));
  
  // Add our own custom class.
  $wrapper_attributes['class'][] = 'custom-css-class';

  if (empty($element['date']['#description'])) {
    $wrapper_attributes['class'][] = 'clearfix';
  }
  // Add an wrapper to mimic the way a single value field works, for ease in using #states.
  if (isset($element['#children'])) {
    $element['#children'] = '<div id="' . $element['#id'] . '" ' . drupal_attributes($wrapper_attributes) . '>' . $element['#children'] . '</div>';
  }
  return '<div ' . drupal_attributes($attributes) . '>' . theme('form_element', $element) . '</div>';
}

This technique can be used for any theme function and is useful when you want to group functionality together into a single module. This also has the benefit of not cluttering up your theme template.php file with lots of overrides.

Of course this isn't all good news. Perhaps the biggest thing to watch out for here is that it might not be clear what function is overriding an element on the page. This can cause some trouble in maintenance as developers will tend to assume that all theme overrides are kept within the theme. To get around this you can grep the code for calls to hook_theme_registry_alter() in order to see where any custom overrides have been added. That can be a bit cumbersome and as such this technique should be used when it is absolutely needed. A good example is when you are creating a module that will be used on multiple sites or themes and that the same override needs to be done in order to create the needed feature. Adding your theme overrides to your module files in this way allows you to activate the module on it's own and not have to alter the theme as well.

Add new comment

The content of this field is kept private and will not be shown publicly.
CAPTCHA
15 + 1 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.