background image
HomeRecent PostsDrupalSearchTagsRSSContactAboutAccount
Eric.London's picture

In order to promote readable and scalable themes (and modules), it's important to plan the organization of your template files and preprocess functions. If you find yourself putting too much PHP code in a template file, you might need to move that code into a preprocess function. On the other hand, if you find yourself putting too much HTML in a function, you might need to create your own template file.

In this code snippet, I'll show you how you can create and define your own template files to be used in your Drupal site. The first thing to do is find a section of your code that has a significant amount of HTML scattered throughout.

Let's take this sample piece of code for example:

<?php
$html
= "";
$myVar = "HTML";
$html .= "<div>HERE IS A LARGE AMOUNT OF <b>"
 
. $myVar . "</b> BEING CREATED.</div>";
return
$html;
?>

For this example, I'm going to convert the statement that creates the HTML into its own template file. I'll start by creating a new file in my template directory called "example.tpl.php". I'll then copy the following into my new file:

<div>
  HERE IS A LARGE AMOUNT OF <b><?php print $myVar; ?></b> BEING CREATED.
</div>

Notice that I had to enclose my variable in PHP tags with a print statement. This is because the template file will be treated as HTML, just like node.tpl.php or block.tpl.php.

Now, we have to let our theme know that we created a template file by defining a hook_theme(). The hook_theme() function resides in the template.php file of your theme, please read more about it here. In your hook_theme() function, you return an associative array of theme implementations along with their arguments and template names.

<?php
function MYTHEME_theme() {
  return array(
   
'MYTHEME_example' => array(
     
'template' => 'example',
     
'arguments' => array('myVar' => null),
    )
  );
}
?>

At this point, you'll have to flush your theme registry before hook_theme() is processes and your new template file is available. Once that's done, you can use the theme() function to call your theme implementation. Let's replace the original example with a theme() function call:

<?php
$html
= "";
$myVar = "HTML";

/*
$html .= "<div>HERE IS A LARGE AMOUNT OF <b>"
  . $myVar . "</b> BEING CREATED.</div>";
*/
$html .= theme('MYTHEME_example', $myVar);

return
$html;
?>

In the above snippet, I used the theme() function to call my template implementation called "MYTHEME_example" and passed it one argument "myVar". As long as you defined arguments in hook_theme(), they will be passed into your template scope. The following will be contents of the $html variable:

<div>
  HERE IS A LARGE AMOUNT OF <b>HTML</b> BEING CREATED.
</div>

If you're like me, you probably use complicated node types with many fields and custom layouts. If that's the case, the default printer-friendly rendering of nodes may not suit you. In this code snippet I'll explain how you can override the templates and how I got to this step.

The first step when making modifications like this is to determine how the html was generated (template, function, callback, etc). Take a look at the query string for a printer friendly page: book/export/html/YOURNODEID. If you check out the book module, you'll see an entry in the book_menu() hook (book/export/%/%) that uses the book_export() page callback. Examining that function leads you to book_export_html(). This function generates the html for printer-friendly layout using the book_export_traverse() function and then calls the theme() function. The theme function allows us to override the theme layer. In this example I'll create a function that follows the templates suggestions guidelines to insert my code...

<?php
// the first argument being passed to theme function is book_export_html, so I'll prefix my function with my theme's name
function MYTHEME_book_export_html($title, $contents, $depth) {
 
// since the node ID is not being passed as an argument,
  // we'll grab it from the query string...
  // load node
 
$node = node_load(arg(3));
   
 
// define a list of node types to override
 
$nodeTypes = array('MYCOMPLICATEDNODETYPE1','MYCOMPLICATEDNODETYPE2');
  if (!
in_array($node->type, $nodeTypes)) {
   
// for all the rest of my node types, I'll use the default template:
   
return theme('MYTHEME_book_export_html', $title, $contents, $depth);
  }

 
// now, you have access to the $node variable and can do what ever you'd like with it
  // note: the drupal_render() function is great for generating the output for a CCK field

 
return "MY NEW AMAZING PRINTER-FRIENDLY HTML";
}
?>

In the above code, the theme registry was overrode by creating a function named: MYTHEME_book_export_html. The last part of this code snippet is returning regular nodes back to the default book template file (book-export-html.tpl.php). In the above code, I mention a theme registry entry called "MYTHEME_book_export_html", which has not been registered yet. We can register a new template using hook_theme()...

<?php
function MYTHEME_theme() {
  return array(
   
// register the default book export template
   
'MYTHEME_book_export_html' => array(
     
// pass it the same arguments, defined in the book_theme() function
     
'arguments' => array('title' => NULL, 'contents' => NULL, 'depth' => NULL),
     
// define the same template as defined in the book_theme() function
     
'template' => 'book-export-html',
     
// since this file does not exist in our theme directory,
      // we'll specify the path of the book module
     
'path' => 'modules/book',
    )
  );
}
?>

Now that we've registered the "new" template (and rebuilt the theme registry) all of our standard nodes will use the original book template file and our custom node types will be modified as defined.

Eric.London's picture

Sometimes the default theming layout of a CCK node cannot simply be adjusted using CSS. In this example, I'll explain how to modify the layout of a CCK node form and group some form elements into a table, item list, etc. NOTE: this code has to reside in you template.php file.

<?php
// first, modify the theme registry so Drupal knows you'd like to override the theming of your CCK node type
function MYTHEME_theme($existing, $type, $theme, $path) {
  return array(
   
'MYCCKTYPE_node_form' => array(
     
'arguments' => array(
       
'form' => NULL
     
)
    )
  );
}

// now, define the function that will modify the structure of the form object
function MYTHEME_MYCCKTYPE_node_form($form) {

 
// create a variable to store the form html
 
$html = "";

 
// loop through the $form object
 
$fields = array();
  foreach (
$form as $k => $v) {
   
// check if the current key is a field
   
if (substr($k, 0, 6)=='field_') {
     
// NOTE: you can use the drupal_render() function to generate the output on $v
      // in this example, I'll create the html for each field and store it in an array
     
$fields[$k] = drupal_render($v);

     
// NOTE: since we're executing drupal_render on $v, and not directly on $form,
      // we'll have to remove this field from $form so it will not be rendered twice
      // Normally, drupal will not render a form object if it has already been rendered
     
unset($form[$k]);

    }
  }

 
// Now the html has been stored in the associative array $fields variable in the format:
  // [field name] => [field html]
  // At this point you could use a theme function
  // (theme_table, theme_item_list, theme_fieldset, etc)
  // to generate the html for a common layout format,
  // or you could create your own html/css layout
  // For example, you could put the fields in an item list:
 
$html .= theme('item_list', array_values($fields));

 
// Lastly, you'll need to generate the html for the rest of the form fields
 
$html .= drupal_render($form);

  return
$html;
}
?>

Here's a screen shot of a CCK node form that I rebuilt in a table:

Syndicate content