Theming a Specific Content Type

Let's start by taking a look at the content type that was defined by CCK.

In this picture we see "Name" and "Type"; notice what it says underneath those fields:

  • Name - "The human-readable name of this content type."
  • Type - "The machine-readable name of this content type."

In order to set up the theme, we need the "Type."

We also need to make note of what we called the "body" field. In this case, it's called "Description."

When we go to "Manage fields" we see this display. The "Name" column is what we need. However, there is one lie here: there is no field called "body filter." This is an unfortunate display of an internal form technique.

Okay, now we have the information we need to start the theme for this content type.

In your favorite text editor, start a new file. It's going to contain both HTML and PHP. And let's start with some basic stuff that will allow us to apply CSS later if we need to.

<div class="content agency">
</div>
<div class="clear-block clear"></div>

What this does is say "Here comes the content for this node and it is also known as 'agency.'" I chose to use the "machine readable" content identifier, however you may call it anything you like. "Content" is a Drupal standard, so you really want to keep that. The second line will just end that division. The third line is not always necessary, but since our content has pictures that may vary in size, it's a good idea to force the browser to return to "ground zero."

Well, I suppose now you want some actual content to show up? Picky, picky.

This content has a picture with it, and a CCK "Image" field uses the ImageCache module. We'd like to put the picture up high and on the left. As a matter of fact, it might be nice if even the company name (title) was even with the top of the picture and to the right of it as opposed to above it. This is not a problem, but does tell us that it should be the first thing we want to display.

We are now inserting stuff after the first <div> tag above.

The ImageCache module provides a theming function that can be pretty handy, so let's use it.

[BTW, the 'echo' function is slightly faster than the 'print' function so I use it even though most themers seem stuck on 'print.' Either will work.]

<?php echo theme('imagecache', 'thumbnail', $node->field_logo[0]['filepath'], $node->title, $node->title, array('align' => 'left', 'hspace' => '10')); ?>

What we've asked for here is to use the display setting called "thumbnail" (use what ever you call yours) to set the size. I'll explain the data field ("$node->field_logo[0]['filepath']") in a moment. The next two "$node->title" parameters say to use the company name for both the "alt" and "title" attributes. And finally, we have an array that tells ImageCache to add the attributes 'align="left"' and 'hspace="10"' to the IMG tag.

Okay, now that we have the picture inserted, it's time for the title. It is fairly standard Drupal practice to use the <h2> tag for this. It is entirely up to you, but that's what I will use here.

The entire node object is available to us in the theme and it is called, by Drupal standards, "$node." Within the node object the title (in our case, the company name) is called, strangely enough, "title." It is therefore referenced as "$node->title."

<h2><?php echo l($node->title, 'node/'. $node->nid, array('title' => t('View agency')));?></h2>

Another "standard" drupal practice is to have the node title be a link to the node itself so that it can be viewed in its "raw" form or edited if you have the permission to do so. So I've used the "l" ("link") function. You can read more about this function here.

For my example here, I've created a table to format the data. You may do this, or use <div> tags to format it with CSS. Some themers like to use definition lists. You are free to use whatever constructs you want. Just be careful that you don't accidentally break your theme.

In my example, I used the CCK_Address module, so I'll explain how its fields are named, since they differ a bit from the other CCK types. The address module allows for multiple addresses; these are part of an array called "field_address" so the first (or only) address is zero (0), the second is one (1), and so on. This module provides several sub-fields whose labels don't necessarily match what is displayed on the entry/edit screen. The field that shows as "Address" is known internally as "street1;" "Address continued" is "street2;" and "Apt/suite number" is simply "apt."

I could not get these fields to use the content formatter ("content_format" function). I don't know if this is good or not, but it's working. More "normal" CCK fields have a safe "view" element available.

<tr><th>Address</th><td>
<?php
 
echo $node->field_address[0]['street1'];
  if (
$node->field_address[0]['apt']) { echo '; '. $node->field_address[0]['apt']; }
  if (
$node->field_address[0]['street2']) { echo '<br/>'. $node->field_address[0]['street2']; }
  echo
'<br/>'. $node->field_address[0]['city'] .', '. $node->field_address[0]['state'] .' '. $node->field_address[0]['zip'];
  if (
$node->field_address[0]['country'] != 'US') { echo '<br/><big>'. $node->field_address[0]['country'] .'</big>'; }
?>

</td></tr>

Notice that I used a little logic to suppress blank lines and to stick the city, state, and Zip code fields together (in US format). I also don't show "US" since I am in the USA and, knowing our postal workers, it would confuse them.

Now, except for the "body" (in our case "description") field, all the other data I have encountered so far is handled the same way. If you refer back to your "manage fields" display for the field names there are only a few more things to know.

A with the "address" above, CCK allows all fields to be single or multiple, so they are always part of an array with the name shown on the "manage" screen. For example, the telephone number is "field_agency_telephone" as we defined it. Also as above, the first element is numbered zero (0), the second is one (1), etc. In our case, we don't really allow multiples, but a simple "foreach" loop could be added to do so.

CCK provides a content formatter, but you don't need it. Each field array has a sub-field known as 'view' that is what the content formatter would produce, but it's already done for you. This is the "safe" way to display the data. An added bonus is that things like email addresses and hyperlinks are already properly formatted for you.

Putting this together, then, means that the "best" way to reference a field is "$field_name[0]['view']," where field_name is the name from the "manage" screen.

<tr><th>Phone</th><td><?php echo $node->field_phone[0]['view'];?></td></tr>
<tr><th>Email</th><td><?php echo $node->field_email[0]['view'];?></td></tr>
<tr><th>Web site</th><td><?php echo $node->field_website[0]['view'];?></td></tr>

Okay, almost done; only one more field to worry about: the "description" (or body) field. This one doesn't follow any scheme that I can fathom, so you have to just take it on faith. This field is referenced as "$node->content['body']['#value']". I've seen some posts that suggest that "$node->description" should work, but it doesn't for me.

So, let's put the whole example together, hopefully as I've described the process:

<div class="content agency">
<?php echo theme('imagecache', 'thumbnail', $node->field_logo[0]['filepath'], $node->title, $node->title, array('align' => 'left', 'hspace' => '10')); ?>
<h2><big><?php echo l($node->title, 'node/'. $node->nid, array('title' => t('View agency')));?></big></h2><br/>
<table border="2" cellpadding="5">
<tr><th>Address</th><td>
<?php
 
echo $node->field_address[0]['street1'];
  if (
$node->field_address[0]['apt']) { echo '; '. $node->field_address[0]['apt']; }
  if (
$node->field_address[0]['street2']) { echo '<br/>'. $node->field_address[0]['street2']; }
  echo
'<br/>'. $node->field_address[0]['city'] .', '. $node->field_address[0]['state'] .' '. $node->field_address[0]['zip'];
  if (
$node->field_address[0]['country'] != 'US') { echo '<br/><big>'. $node->field_address[0]['country'] .'</big>'; }
?>

</td></tr>
<tr><th>Phone</th><td><?php echo $node->field_phone[0]['view'];?></td></tr>
<tr><th>Email</th><td><?php echo $node->field_email[0]['view'];?></td></tr>
<tr><th>Web site</th><td><?php echo $node->field_website[0]['view'];?></td></tr>
</table>
<?php
 
if ($node->content['body']['#value']) {
      echo
'<p><big>'. $node->content['body']['#value'] .'</big></p>';
  }
?>

</div>
<div class="clear-block clear"></div>

While I initially developed this on the Bluemarine theme, it worked without change on the Garland theme, except I needed one small addition to my CSS: ".agency table {width: auto;}". This is because the Garland theme has the annoying habit of ignoring my "width" attributes and forcing 100%.

And Finally...

The time has come to save your work and see if it's right.

Save the file into your active theme folder as "node-content_type.tpl.php" where content_type is the internal content type name that you made note of in the first step.

Showing a List of the Nodes

The site already had Views, so that allowed me to use that module to select the content type, although I think it's an overkill.

But the fields are already selected in the theme, so what do I need to do in Views? Well, I found that all that's needed for a "teaser view" is to select any (one) field in the node, such as the title, and you will get the correctly themed node. You might also want to provide a sorting order, but that's not the point here.