Some lessons from building Drupal 6 themes
After a few Drupal 6 projects where I had to create themes from scratch, including my recently released Woodpig theme for Ventanazul I've learned a lot and decided to gather some tips I'm sure will help you, my fellow Drupalist, when turning your next design into a functional Drupal managed site. Sounds good? Let's dive into the powerful Drupal 6 theme API.
<!--break-->
Get the documentation right
There are a lot of changes in the Drupal 6 theming API and even being quite familiar with Drupal 5 theming I had to invest a considerable amount of time to get all the new ideas; hence, the theme guide for Drupal 6 should be your first stop.
But quickly reading the theme guide for Drupal 6 once may not be enough, topics like preprocess functions and registering hook themes still confused me after my first try at the documentation so I had to re-read many parts. My advice is to read the guide at least twice, the first time just try to get the idea and the second one start playing with a test install of Drupal 6 to see how the new theme API works.
You always need to register theme functions and templates
Drupal 6 calls these theme hooks. A theme hook is something that can be themed, usually output from your modules that may need alternative HTML or CSS. In Drupal 5 you could write a module and add as many theme functions as you wanted, these functions could then be overriden by any theme or converted into a tpl.php file using the _phptemplate_callback function.
Drupal 6 needs to know which theme hooks are available in your theme beforehand; this is to be more efficient and avoid discovering theme hooks on run time. To register theme hooks you should use the new hook_theme. Here you can tell Drupal what theme functions or templates your module will provide and what variables can be used.
Here's some code from the custom module I use in Ventanazul:
function ventanazul_theme() {
return array(
'ventanazul_flickr' => array(
'template' => 'ventanazul-flickr',
'arguments' => array('flickr_user' => NULL, 'attributes' => array()),
),
'ventanazul_google_search' => array(
'arguments' => array('publisher_id' => NULL, 'attributes' => array()),
),
);
}
The first hook, ventanazul_flickr, will use a file called ventanazul-flickr.tpl.php as a template that will have a couple of variables available: $flickr_user and $attributes. This is what I use for showing some pictures from my Flickr account.
The second hook, ventanazul_google_search does not provide a template and that means it will just use a theme function with the following signature: theme_google_search($publisher_id, $attributes). Notice how the variables in this case are passed directly to the theme function.
Don't forget to clear the theme registry
The theme registry is where Drupal 6 stores what it knows about the available theme hooks, every time you modify theme hooks you need to clear it or your changes won't be recognized. Clear theme registry in any of these ways:
- Visit Administer > Site configuration > Performance and click on Clear cached data. This is my preferred way and I always keep a tab open in Firefox just for this page.
- Use the devel block from the devel module and click the Empty cache link.
- Call the function drupal_rebuild_theme_registry.
Define regions for blocks in .info files
For Drupal 5 you added your theme's regions in template.php, now you just need to add a few lines to your theme's .info file. This code is from a theme called Winds of Change that I've just finished coding for a client:
name = Winds of Change
description = Site of Winds of Change Group
version = VERSION
core = 6.x
engine = phptemplate
stylesheets[all][] = style.css
stylesheets[print][] = print.css
regions[left] = left sidebar
regions[right] = right sidebar
regions[content] = content
regions[header] = header
regions[footer] = footer
regions[social] = social
Notice the regions entries by the end, this is plain text but the syntax is similar to PHP arrays. These lines tell your theme to make those regions available and will use the names in brackets as variable names; regions[left] will create a $left variable for your page templates.
Changes in your theme's .info files require clearing the theme registry as explained above. Actually, I'm a little paranoid and everytime some of my theme changes are not reflected on the site I clear the theme registry.
Cache and theming for authenticated and anonymous users
This is a minor but very important detail. Drupal caches it's output for anonymous users and may optimize your CSS and Javascript files. Review your settings in Administer > Site configuration > Performance to disable all caching and optimization while you are developing a new theme and clear the theme registry to see your changes as an anonymous user.
Do your best with the available theme hooks
There are many times when you are tempted to override a certain theme function or template in Drupal 6. This is what I tried to do when theming navigation links for Ventanazul but after thinking a little more I realized I could get the same results with some smart CSS and a few additional variables. For example, for the bottom navigation, I just used:
print theme('links',array_merge($primary_links,$secondary_links), array('id' => 'alternate-menu'))
theme_links is provided by Drupal core. Notice how I merge primary and secondary links and apply an id alternate-menu to be styled from CSS inside an #alternate-menu rule.
Before overriding a theme function or template check the source and see if you can pass some variables to affect it's behavior.
Use templates from modules
Drupal 6 recommends all module developers to provide templates, that means you should check in your module's directory to see what .tpl.php files are available and copy the ones you need to modify to your theme's directory. For the Woodpig theme I copied user-profile.tpl.php from modules/user to make a few changes in user account pages. Remember to clear the theme registry after adding a tpl.php file to your theme.
You can create theme hooks for modules you did not write
When I wanted to theme the comments form the first thing I did was looking at comment.module. There I found a call like this:
drupal_get_form('comment_form', $edit, $title)
Ok, I had a comment_form function that I could theme according to Drupal Form API, then I started looking for theme_comment_form but there was none.
How to override a theme hook that does not exist? Just as you do with your own modules, you just need to know the code involved and, this is important, you can include a hook_theme function in your template.php file. This is what I did for Woodpig:
function woodpig_theme() {
return array(
'comment_form' => array(
'arguments' => array('form' => array()),
),
);
}
The function starts with the theme name and the theme hook takes the form argument because it's a that's what drupal_get_form requires. Then I can write my own woodpig_comment_form($form) function.
Behold the power of preprocess functions
Preprocess functions allow you to modify the variables available to your templates. You can also add new variables. This is pretty important and I use it a lot for things like customizing breadcrumbs, passing the theme's path to my templates and separating the comment form from the content of a node. I suggest you play a lot with preprocess functions and understand their power. I'll have a few more articles about them soon.
Far from complete
There are still many lessons to learn from the Drupal 6 theme API and I find more and more in every new project. Even if this wasn't intended to be a complete guide I'm sure these ideas will help you in your next theming adventure.
Let me know what you think in the comments and if you have more questions or suggestions for coding with Drupal pay a visit to the Drupal forums in Ventanazul. Happy coding!