A peek at Drupal 7 theme system changes

[Update Feb 20, 2011: I recently presented an update of my Grok Drupal (7) Theming session at Drupal Design Camp Los Angeles. A write-up with links to the slides and video can be found in the post titled (naturally enough) "Grok Drupal 7 Theming - Update".]

[Update January 2011: Drupal 7.0 has been released!]

Every major release has introduced significant changes to the core API. There's no backwards compatibility between major Drupal releases; you can't run Drupal 7 modules on a Drupal 6 site, for example. In fact, if you tried you'd likely end up with the white screen of death. This willingness to let go of past thinking and old code has allowed Drupal to be as awesome as it is today.

For theming, fortunately, the changes have usually been rather incremental — a short checklist of minor changes to themes to get them working in the newer Drupal. And none was as radical or challenging as the change from Drupal 4.5's xtemplate theming engine to the phpTemplate engine that emerged around Drupal 4.6 and became part of Drupal 4.7's core. That "upgrade" actually required total reimplementation. You simply could not just tweak an xtemplate-powered theme into a phpTemplate-powered theme. Thankfully, since then it's been a much easier road for theme upgrades.

That is, until now.

PINGV CreativeMany Drupal themers will be daunted by the changes in Drupal 7. But we're excited about the new power.

50 (or so) drops make for an ocean of changes

Drupal 7 represents just about as big of a theme refactoring as possible, without replacing the core phpTemplate theming engine. I won't say this upgrade is harder than the xtemplate-phpTemplate migration. No, we're still working with the same basic concepts, same general approach — in theory, anyway. But the changes are still enough that anyone looking at doing a theme upgrade from Drupal 6 to Drupal 7 might seriously consider simply rebuilding it from the bottom up, rather than trying to apply this change here, that change there … for 50-something changes.

At my Drupal theming presentation at DrupalCon San Francisco, I touched upon some of the big things that D7 theming is introducing. There's video on the DCSF site. Here are my slides:

But a more detailed rundown of the changes are (mostly) detailed at drupal.org/update/theme/6/7.

There are 49 changes documented so far, as I write this (with Drupal 7 still in alpha), and a few others to boot. In fact, some changes are not documented there yet or are actually incorrect. (See bottom of this post for links to issues where I and others are working to correct some of these.)

So without trying to be comprehensive, here is an introduction to some of the Drupal 7 theming changes that will make Drupal theming even more powerful:

html.tpl.php

As I write this, nothing about html.tpl.php is documented in "Convert 6.x themes to 7.x" in the Drupal.org handbooks (and this is something I'm working to change). html.tpl.php is now the new page wrapper for your theme. Here you have the DOCTYPE declaration, the <head> info, and page closer.

modules/system/html.tpl.php contents

Here's a marked-up image of what you find in html.tpl.php:

The html.tpl.php template in Drupal 7 takes over the webpage container duties previously managed by page.tpl.php in Drupal 6. In Drupal 7, the latter template contains pretty much all of what you see on the screen; the <head>, <html> and <body> tags, as well the DOCTYPE declaration, are all moved to the html.tpl.php file. The page.tpl.php template is actually called from within the html template. Note: You can always find the base templates Drupal is sending your way by finding the *.tpl.php files within Drupal core's modules' folders, and in most of the contributed modules.

As you can see, this template has taken up the markup and renderings for pretty much all of the meta wrapping around the actual page that gets displayed, including new items $page_top and $page_bottom. page.tpl.php gets called this way:

<?php
print $page;
?>

This template is pretty straightforward, and something most themers won't even have to deal with. But if you forget this template, you're going to run into some problems. So remember to include it. But if you include it for the purposes of override, be careful or you could run into problems. [Thanks to Pasqualle's comment for correcting me on this!]

More: api.drupal.org documentation on html.tpl.php and an issue for documenting html.tpl.php implementation and changes.

region.tpl.php

diagram of nested templatesRegions templates contain the content area, sidebars, footer, etc. By default, they follow Drupal core's region.tpl.php template.

Yes, regions now have templates. The active code Drupal 7 core provides for region.tpl.php is this:

<div class="<?php print $classes; ?>">
  <?php print $content; ?>
</div>

As usual, you declare your regions in the theme's .info file. If you don't declare regions, you will get these by default:

The various regions are called out and rendered by code in the page template, page.tpl.php, by using a syntax identifying the region as a variable, e.g.:

<?php print render($page['help']); ?>
<?php print render($page['sidebar_first']); ?>
<?php print render($page['sidebar_second']); ?>

Of course, to conditionally print the region only if it has stuff to display, it might look something like this:

<?php if ($page['highlight']): ?><div id="highlight"><?php print render($page['highlight']); ?></div><?php endif; ?>

More: api.drupal.org documentation on region.tpl.php.

Hidden regions

This is a neat little one. It's a way to define regions that are available for modules but are not available in the blocks administration area. Why would you want to do that? The Drupal.org Handbook's theme update page explains a use case:

The page_top and page_bottom regions are hidden, which means they will not show up on the blocks administration interface. When doing site-specific themes, it might also be useful to add more hidden regions (to provide ways for modules to add output to more places in the theme without defining blocks showing up on the blocks interface), you can do that via the regions_hidden[] .info file array which is new to Drupal 7:

theme .info file extract:

regions[content] = Content
regions[help] = Help
regions[page_top] = Page top
regions[page_bottom] = Page bottom
regions[indicators] = Indicators
regions_hidden[] = indicators

In this above example, the region named "indicators" is additionally defined as a hidden region.

Two ways of hiding elements with CSS

Speaking of hiding things, there are now two standard ways to hide content using CSS:

  1. .element-hidden — to hide an element from all users. ("An example would be a collapsible fieldset that will be expanded with a click from a user. The effect of this class can be toggled with the jQuery show() and hide() functions" — handbook.)
  2. .element-invisible — to hide an element visually, but keep it available for screen-readers. ("This class should be used for information required for screen-reader users to understand and use the site where visual display is undesirable.... This class must not be used for focusable elements (such as links and form elements) as this causes issues for keyboard only or voice recognition users" — Ibid.)

It's extremely handy to have these predefined, especially as browser-viewed design patterns get increasingly complex and specific to particular use cases. All themers have to recognize that they are implementing an application interface, not a "web page" as conceived by many since the advent of Netscape in 1994.

All titles in templates get prefix and suffix

Thusly:

<?php print render($title_prefix); ?>
<?php if ($title): ?><h1 class="title" id="page-title"><?php print $title; ?></h1><?php endif; ?>
<?php print render($title_suffix); ?>

Why?

One reason is to generate contextual links — such as a handy block edit link right there where the block is displayed, visible to and accessible by users with appropriate permissions.

Granular display of $content info in node.tpl.php

Now this part is really cool. It no longer requires preprocess to break apart node $content output. Of course, you could print each field on its own, but what a pain in the ass!

But in Drupal 7, you now have two new functions for printing out content in a node or user profile:

  • render()
  • hide()

Let's look at an example taken from node.tpl.php:

<div class="content">
  <?php
   
// First we hide the comments and links now so that we can render them later.
   
hide($content['comments']);
   
hide($content['links']);

    // Then we print the content. Comments and links end up not included here.
   
print render($content);
 
?>
</div>
    // Then we print the links and comments separately

<?php print render($content['links']); ?>

<?php print render($content['comments']); ?>.

What this means is that you can pull components or fields out of your template's big $content blob and display them in a more controlled way — e.g., to split them out of the "content" div, as illustrated above.

Fields are in core

Photo of a fieldWith fields now in core, if you weren't CCK-prolific, you have more theme components to account for by default.

This isn't so much a theming-specific thing, but it's really cool! The field.tpl.php template is pretty straightforward:

<div class="<?php print $classes; ?> clearfix"<?php print $attributes; ?>>
  <?php if (!$label_hidden) : ?>
    <div class="field-label"<?php print $title_attributes; ?>><?php print $label ?>:&nbsp;</div>
  <?php endif; ?>
  <div class="field-items"<?php print $content_attributes; ?>>
    <?php foreach ($items as $delta => $item) : ?>
      <div class="field-item <?php print $delta % 2 ? 'odd' : 'even'; ?>"<?php print $item_attributes[$delta]; ?>><?php print render($item); ?></div>
    <?php endforeach; ?>
  </div>
</div>

In there, like in Drupal 6's filefield module template, it's pretty clear where the <div>s are, so you can get at your markup output.

The box is dead

Ding dong! box.tpl.php is no more. What was the box for? Good question! Moving on….

Search in the theme is dead! Long live search in a block!

Because it seemed silly to have search live in both places, the search form call in page.tpl.php is no more. You can place the search form using its available Drupal block.

Template suggestions' delimiter change

Template suggestions now use a '--' delimiter rather than the '-' that was used in Drupal 6. To illustrate:

  • page--front.tpl.php
  • node--blog.tpl.php
  • node--event-performance.tpl.php

Note that word separations within an element remain '-'. This allows for cleaner logic in handling instances of hyphenated elements, so the double hyphen always indicates a more targeted override of what comes before the '--'.

preprocess, process and process_html

This is a change that really deserves its own blog post (and a long one at that). Suffice to say that preprocess no longer stands alone as a high-power tool for themers.

There are now two sets of variables process functions. The first are the existing "preprocess" functions. The second are "process" functions which are run after preprocessors. All the various prefixes and suffixes apply to this second phase in the exact same way. This is useful when certain variables need to be worked on in two phases.

For example, adding classes into an array for the "preprocess" phase then flattening them into a string in the "process" phase so it's ready to print within a template.

Also there's now preprocess_html. Documentation issue: drupal.org/node/579698

This alone should be enough to inspire the dedicated Drupal themer to brush up on her PHP chops.

RDFa

Robots kissingRDFa is here to stay. Robots will love it. You should just stay out of the way.

Providing extra rich robot food in every pageload is one of Drupal 7's big improvements, and that means new RDFa requirements for themers so that this layer of robot food gets loaded into the page. Here's what you want to make sure you do in your html.tpl.php template :

  1. Declare the appropriate "XHTML+RDFa" doctype:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN"
      "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">
  2. Print appropriate <html> info, language attribute, and RDF namespaces:
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php print $language->language; ?>" version="XHTML+RDFa 1.0" dir="<?php print $language->dir; ?>"
      <?php print $rdf_namespaces; ?>>
  3. Print the GRDDL profile to facilitate consumption of this RDFa metadata:
    <head profile="<?php print $grddl_profile; ?>">

    See: Wikipedia info on GRDDL.

Yes, I know, this may not seem like it's relevant to a themer, but this is the future. Drupal is leading the way in CMS adoption of RDFa, which will lead to a future where the web increasingly behaves like one big machine with one big database of information. So feed the robots.

A couple of open issues

Conditional stylesheets

This is a big issue for me, and I believe for all Drupal themers: There's a question whether themers should be able to declare conditional stylesheets in the theme's .info file.

The alternative, placing code in a preprocess function, requires a modicum of PHP knowledge, which is not ideal for front-end development. Yes, there is a lot in Drupal 7 theming where PHP knowledge is extremely helpful, I know.

But conditional stylesheets, in this day and age of multiple platforms, are such a common use case it would be great not to have to bury that implementation in preprocess. Conditional stylesheets should be easy peasy to declare!

If you think so, too, please let it be known on this issue.

Documenting all of this

This blog post is I hope a helpful read for Drupal themers and front-end developers interested in working with Drupal. But it is not documentation. The documentation on Drupal.org is incomplete and inaccurate in places. Here are some issues where you might help.

This is only a start

In the coming weeks, I hope to find the time between client work and company business (and helping with testing and documentation of Drupal 7) to follow up on this post with more highlights and tips on Drupal 7 theming. If you've come across other really cool bits, please share them here.

Permalink

Comments

Pasqualle (not verified) (3 May 2010 - 2:00am)

html.tpl.php

But if you forget this template, you're going to run into some problems. So remember to include it.

I do not understand this. One of the main reason of separation is that you do not need to change this part of the html source. The default html.tpl.php should be sufficient in 99% of the cases, so themes should not contain this template.

Laura Scott (3 May 2010 - 7:34am)

Excellent point. I expressed myself poorly and have corrected the language in that passage. Thanks!

Sina (not verified) (3 May 2010 - 2:40am)

Great! thanks for sharing.
Drupal 7 really rocks, i always had trouble theming fields

adrinux (not verified) (3 May 2010 - 3:07am)

Thanks for that roundup, I hadn't realised the changes were so wide ranging! More power and flexibility, but it seems a little extra verbosity – a price worth paying.

Have already commented on the conditional stylesheet issue, and am watching that one.

robin (not verified) (3 May 2010 - 7:41am)

really cool intro to d7 theming. want more :-)

moshe weitzman (not verified) (3 May 2010 - 8:26am)

Awesome slides. Thanks for the drush advocacy as well.

I think at least a mention of hook_page_alter() is in order. Here themers can adjust structure before any theming takes place. Its very handy, and in D7 themes can implement alter hooks.

Laura Scott (3 May 2010 - 8:49am)

Yes indeed, this is awesome!

There are so many awesome changes. I think I'm definitely going to have to take on a part 2 to this post soon.

Thanks!!!

rocksoup (not verified) (3 May 2010 - 9:55am)

Great article! Thanks.

Jeremy Epstein (not verified) (3 May 2010 - 9:57pm)

I think it's really bad that in many places where non-developer themers could previously use:

<?php print $something; ?>

They will now have to use:

<?php print render($content['something']); ?>

Themers are now being asked to learn the syntax of a function call and of accessing an associative array element, where they were previously being asked only to learn how to output a string.

The issue of tpl.phps are not templates has just gotten worse.

Laura Scott (4 May 2010 - 8:11am)

I somehow had missed Bevan's post and slides. He makes some good points.

Maybe for Drupal 8, we can work towards separating out the PHP programming and let the templates truly control markup. That would be cleaner and makes more sense than forcing people to do elsewhere, e.g., in preprocess.

But there's no questioning that there's incredible power available here. And given the choice of having markup but little other control vs. having all kinds of control, as we do now, I take the latter. It may hurt adoption of Drupal by front-end developers, but it won't hurt how rockin' cool the Drupal UI can be in D7 websites and webapps.

Drupal Theme Garden (not verified) (4 May 2010 - 7:42am)

Nice overview, thanks.
It seems that drupal theming subsystem has (r)evolution again.

Question:
is it unable to have search-box implemented directly in page.tpl.php ?

Andy Walters (not verified) (4 May 2010 - 8:05am)

Thanks Laura! This was helpful.

Cary Gordon (not verified) (5 May 2010 - 12:38pm)

Thanks for this really great walkthrough. Looking forward to part 2!

Sharon - Drupal Themer (not verified) (8 June 2010 - 7:57am)

Thanks for sharing this. I'll start converting our drupal 6 themes to drupal 7.

Adrian (not verified) (4 September 2010 - 8:13pm)

Still don't quite understand the hidden region

Adrian (not verified) (4 September 2010 - 8:45pm)

Is it easy to output some of fields in node to another region ?
For example, $links in node output to another region

Gokhan (not verified) (7 October 2010 - 5:18pm)

Hello Mrs. Scott
Thank you for content
it's really useful. I have a website and i'll try to convert it to drupal but there is no importer yet.

boutdepapier (not verified) (24 October 2010 - 12:05pm)

Not sure to get the interest of a region...

For example if I define a new region like region--header.tpl.php (and it is in the default list):
I won't be able to call the $logo... But a site logo is most of the time in the header...

Drupal Themes (not verified) (28 December 2010 - 1:07am)

I have been using Drupal for my website and blog for almost a year now and I am using a Drupal 6 template. My template is faster and accurately downloaded every time my website is opened. Will Drupal 7 template be working the same?

Josh Bryant (not verified) (9 January 2011 - 10:41pm)

Thanks for the great post!

Quick question (if you don't mind): How would you create an html.tpl.php specific to a certain node type?

Using regular template overriding, I've been able to specify the html.tpl.php file for a specific node (html--node--1.tpl.php) but I haven't been able to specify for a specific node type.

I stumbled upon this method for Drupal 6 ( http://drupal.org/node/223440 ), and tweaked it to use my_theme_preprocess_html(&$variables) instead, but it doesn't appear to be working.

Any suggestions?

Fahad (not verified) (4 July 2011 - 8:12am)

It's really one of the most useful resource to understand the theming differences between drupal 6 & 7.

One thing I would like to know is how can we print field_something in page.tpl.php

like in node.tpl.php you have told that we can use the following syntax

<?php print render($content['field_something']); ?>

Laura Scott (4 July 2011 - 9:23am)

Page template does not have node variables available, and if you think about it, that makes sense. Which node? A page template may load several nodes.

If you're trying to print a field out in a different part of the page, there are several ways to go. One is to create a view of the field using the current node as an argument, with a block display, and place that block on the page with the node. Another approach is to use Panels. There are other ways as well.

Post new comment

The content of this field is kept private and will not be shown publicly.