File-Based Content Templates


John Forsythe recently posted that Contemplate is one of the most favorited modules on his site, attributable to the learning curve of creating template files.

With no offense to Jeff Robbins who created this very clever module, I've never been a big fan of Contemplate. I don't like it when people store code and markup in the database because it makes it impossible for me to find where their markup comes from when I search the files. It also puts it out of the reach of version control, and creates the possibility of a white-screen situation which can only be fixed by going into the database directly.

But contemplate fills a real void created by the difficulty in per-field theming in Drupal 5. I posted about my method of handling per-field theming in content types recently and have been refining that method since. The purpose of my technique is to allow per-field custom theming without requiring additional work every time a new field is added. Essentially you print out any fields you would like to custom theme, and then you loop through all the other fields and print them all out in the order and with the 'Display Fields' settings as set in CCK. Although this method requires additional setup up front, it pays off when you add new fields (or modules that add new node content of their own) and you don't have to readjust your template (or contemplate).

A client recently asked me "I'm using both contemplate and template files. Which one overrides which?" I don't have much experience with contemplate so I told him that basically "If you have to ask that question you know you're in trouble!" and advised to remove contemplate module. With further investigation, I see that contemplate seems to actually only control the $content variable of a node template (Is this correct? Hence its name no doubt.) Therefore it would not be completely nonsensical to use both contemplate and node templates so long as you left the $content variable in tact in your node templates.

This observation led me to the further observation that overriding the $content variable is really the only thing that people want to do in their node templates in general. I don't create a separate node-story.tpl.php and node-page.tpl.php and node-crazyform.tpl.php because I want to separately switch around the taxonomy or links sections. Much more likely I copy node.tpl.php and then override the $content variable, leaving everything else in tact. So in fact I don't ideally want to create a node-story.tpl.php- what I really need to create is a content-story.tpl.php. Such a template file would serve the purpose of contemplate while avoiding its markup-in-the-database pitfall.

To create a file-based content template system, you need to override the $content variable from within your template.php. This is done from the function _phptemplate_variables. Because I usually use Zen theme (you're back on top jjeff!) I override the variable within its phptemplate_preprocess_node function instead of from _phptemplate_variables. (You will find this function ready to be uncommented in your zen subtheme's template.php)

function MYTHEMENAME_preprocess_node(&$vars) {
$vars['content'] = _phptemplate_callback('content', $vars, array('content', 'content-'. $vars['type']));

Here you see I have overridden the node template's usual $content variable with a phptemplate callback. The callback will look for content-TYPE.tpl.php pages, defaulting to a content.tpl.php.

In my content.tpl.php I can put my per-field content theming code. I have improved it (i.e. made it even more complicated) since my first post on the idea.

My content.tpl.php contains:

$fields = $node->content;
* add custom content field theming here
* unset each field after printing
* for example
* print '

* print $fields['field_special']['#value];
* unset($fields['field_special']['#value]);
* print $fields['field_another']['#value];
* unset($fields['field_another']['#value]);
* print '


//loop through all remaining fields and print their values
if (is_array($fields)) {
foreach($fields as $field) {
//print field values
if ($value = $field['#value']) { print $value; }
//loop through groups
elseif (is_array($field)) {
foreach($field as $groupfield) {
if ($value = $groupfield['#value']) { print $value; }
} ?>

This code loops through the pieces of $node->content, printing them all out with respect to the order and display settings you defined with CCK. It should handle fields that are within groups as well. With this template your nodes should look exactly as they did without it and when you add new fields and groups they will appear without further adjustment.

The portion of the template that is commented out is an example of how to use the template to do a custom output of certain fields. In the example two specific fields are put inside of a new div (a frequent need). Each field has to be unset after it is printed so that it is not printed again during the foreach loop that prints out each field. When setting up these custom per-field outputs you will want to be looking at the "Devel Render" tab of your node (use Devel module) for reference.

Unfortunately there is no really simple way to do these things in Drupal 5 and while I find this technique saves me time I realize that it may look less than simplistic to a non-coding themer. I hope it gives others further ideas of how to better simplify node content theming.

Ready to get started?

Tell us about your project