Fun Theming All Node Edit Forms at Once

Profile picture

Drupal 6 provides many avenues to modify its appearance, including ways to theme a form. Recently I themed all node edit forms at once. I will share here how I did it.

The usual route to theme a node edit form, or any form for that matter, is to grab the form ID, which is used as a theme hook, and implement a hook_theme function in template.php in the theme.

<?php
function MYTHEME_theme() {
  return array(
   
'blog_node_form' => array(
     
'arguments' => array('form' => NULL),
     
'template' => 'blog-node-form',
    )
  );
}
?>

This lets you alter the theme registry to add a template file for a specific form. There are many blog posts that go into more detail than I do here on how to theme forms in general.

The above is useful if I want to theme a blog content type or the user registration form, for example. Most sites have at least a few different content types. I wanted to make alterations to all node forms at once and use a single template file. This involved a slightly different approach.

Correction: This next step with the theme_registry_alter example is not necessary. See comments below this post.

There is a theme_node_form theme function in Drupal, which is called by all node forms. I wanted to replace or supersede this theme function with a template. I tried to do this with hook_theme, but it continued to pick up some parts of theme_node_form in addition to supporting my template file. So it called the form array twice, causing recursion; which is not what I expected. So instead I took a colleague's suggestion and altered the theme registry via hook_theme_registry_alter(). This seemed clean and simple enough.

This takes the node_form theme hook and changes it from a theme function to a template, in sites/all/themes/MYTHEME/template.php:

<?php
function MYTHEME_theme_registry_alter(&$theme_registry) {
 
$theme_registry['node_form'] = array(
   
'template' => 'node-form',
   
'arguments' => array('form' => NULL)
  );
}
?>

This worked great. But I hear that hook_theme_registry_alter is intended for use in modules only? If you know why, please chime in with a comment. Or leave a comment on the theme registry for special cases handbook page.

And the fun part... Spiffing up the node edit form

Here is what I did. This creates two columns for the form and places all top level form fieldsets on the right.

Screenshot:

Screenshot of modified node edit form.

And here is how I did it...

Markup in the template sites/all/themes/MYTHEME/node-form.tpl.php:

<div class="node-form-left">
  <?php print $form; ?>
  <?php print $buttons?>
</div>
<div class="node-form-right">
  <?php print $fieldsets; ?>
</div>

A little CSS for the columns:

.node-form-left {
  float:left;
  margin-right:2%;
  width:640px;
}
.node-form-right {
  float:left;
  width:300px;
}

Peprocess function in sites/all/themes/MYTHEME/template.php:

<?php
/**
* Preprocess for node-form.tpl.php
*/
function MYTHEME_preprocess_node_form(&$vars) {
  foreach(
$vars['form'] as $key => $value) {  
   if (
is_array($value) && $value['#type'] == 'fieldset') {
     
$vars['fieldsets'] .= drupal_render($vars['form'][$key]);
    }
  }
 
$vars['buttons'] = drupal_render($vars['form']['buttons']);
 
$vars['form'] = drupal_render($vars['form']);
}
?>

So what this does is create and prepare a few variables for my template. First it loops through the form array grabbing all fieldsets and puts them in $vars['fieldset'];. I then print this variable in the template. Similarly, it creates a buttons variable that grabs the submit buttons. There are a lot of ways to view the array, but I usually use print_r($vars['form']); in my preprocess function when I want to see what's happening behind the scenes.

As alternative route to do all this is with a theme overwrite function, which would look like this (I did not test this snippet!):

Update: See this comment for a version of this snippet that works.

<?php
/**
* Theme overwrite of theme_node_form();
* Note, this is UNTESTED CODE, but it should work.
*/
function MYTHEME_node_form($form) {
 
$output = '<div class="node-form-left">';
 
$output .= drupal_render($form['buttons']);
 
$output .= drupal_render($form);
 
$output .= '</div>';
 
$output .= '<div class="node-form-right">';
  foreach(
$form as $key => $value) {  
   if (
is_array($value) && $value['#type'] == 'fieldset') {
     
$output .= drupal_render($form[$key]);
    }
  }
 
$output .= '</div>';
  return
$output;
}
?>

So I could have used a theme overwrite instead of manually altering the theme registry. Theme functions are five times faster than using a template, so often it's recommended and preferable to use a theme function.

So there you go. That's how I themed all node edit forms with a template and preprocess function.

Comments

Rubio, Thanks for the article.

I am trying change the position of comments in a node (story content type) with the techniques you have used in this article. I installed devel module to examine the available variables. But i cannot find 'comments' variable to pass it to the node-story template file.

what could be wrong?

Give the man a prize! You've encountered one of the more frustratingly not very "drupal way" parts of Drupal.

The menu callback for node/(nid) runs to node_show() which somewhat inflexibly appends comment_render() to the output.

There are a number of kludgy workarounds (including CSS-based fixes), but if you really want to do interesting things in terms of placing your comment thread you're probably going to end up with one of these:

1) Dynamically "turning comments off" during the normal part of the node_show (e.g. setting $node->comment = 0 via a nodeapi hook) and then doing something in your theme layer to call comment_render directly and deal with its output there. This is pretty hacky, because you're "tricking" drupal by flipping the node->comment bit around. This was (I believe) the only way in Drupal 5.

2) Implementing a hook_menu_alter() to substitute your own function for node_show(). This is clean, but you end up taking a lot of responsibility since you now own the render action for every node view.

3) Using Panels (or, actually the Page Manager withing the Ctools library) to take a systematic approach to #2 above. Page manager allows you to cleanly override the node show menu path conditionally, and provides all the groovy Panels Drag'n'Drop goodness (plus the framework for custom layouts, content bits, etc) for laying out your node view. It ships with the comment thread as a ready-to-place content pane. :)

And, it should go without saying that my favorite plan is #3.

Thanks Josh for explaining it in detail. I will explore option #3. I think, we have taken care of this issue in D7, where we can control 'comment' variable in template files.

Drupal 7's node_show is quite a bit more flexible, as is the whole rendering pipeline.

http://api.drupal.org/api/function/node_show/7

Thanks for writing this, what I like specially about this post is that you explore every way of doing it. This is why we love Drupal, and why it confuses a lot of new people coming in... there are always more than one way of doing things!

About how when to choose using a theme registry alter, or a theme override function -- depends on your themer's PHP knowledge. If your themer is not comfortable within <?php ?>, then have a developer use the registry alter and provide a tpl file and easy to print variables. Otherwise, theme override is better and faster.

I explored every way of doing it, but I think it's funny and illustrative that I was wrong about one of the ways, as Nate above pointed out. I'm not even that new to drupal and I was confused.

I don't think your final example will work as you expect. drupal_render($form) is going to render everything in the $form array that hasn't already been rendered including the fieldsets which you then re-render so they'll appear twice. So what you need to do is something like

<?php
  $fieldsets
= '';
  foreach(
$form as $key => $value) { 
    if (
is_array($value) && $value['#type'] == 'fieldset') {
     
$fieldsets .= drupal_render($form[$key]);
    }
  }
 
$output = '<div class="node-form-left">';
 
$output .= drupal_render($form);
 
$output .= drupal_render($form['buttons']);
 
$output .= '</div>';
 
$output .= '<div class="node-form-right">';
 
$output .= $fieldsets;
 
$output .= '</div>';
  return
$output;
?>

Geesh and I made the same mistake with the buttons. Here's a better version:

<?php
  $fieldsets
= '';
  foreach(
element_children($form) as $key) {
    if (
$form[$key]['#type'] == 'fieldset') {
     
$fieldsets .= drupal_render($form[$key]);
    }
  }
 
$buttons = drupal_render($form['buttons']);
 
$output = '<div class="node-form-left">';
 
$output .= drupal_render($form);
 
$output .= $buttons;
 
$output .= '</div>';
 
$output .= '<div class="node-form-right">';
 
$output .= $fieldsets;
 
$output .= '</div>';
  return
$output;
?>

Awesome! I'll update to point to your version. :)

There is a theme_node_form theme function in Drupal, which is called by all node forms. I wanted to replace or supersede this theme function with a template.

You shouldn't need to make a hook_theme() or even a hook_theme_alter() in template.php to override a theme function with a template. You can just make a node-form.tpl.php and the theme system will find it automatically after clearing you caches. I bet you can delete your second snippet from this article and everything will continue to work exactly the same as with it.

You're right! I also re-read the documentation. So yes, if you need to convert a theme functions to a template, you just need to create a template file with the same hook as the theme function, but with dashes. It's explained here. I can't believe I had this confused.

Thanks for the correction!

Hi Squiggy, sorry, this is a bit embarrassing, but I lost your mail again. I´ll be in San Francisco some days in mid September, send me a mail if you like. (and then delete this comment if you can... sorry!)

Add comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <br> <br/> <br /> <p> <img> <blockquote> <i> <b> <u>
  • Lines and paragraphs break automatically.
  • You may post code using <code>...</code> (generic) or <?php ... ?> (highlighted PHP) tags.
  • Web page addresses and e-mail addresses turn into links automatically.

More information about formatting options

By submitting this form, you accept the Mollom privacy policy.

Client Testimonial

We have found Chapter Three to be a valuable resource in both training and development of custom Drupal functionality. In addition, getting Chapter Three’s help with scaling Drupal has been a real asset.

Jim Nisbet, CTO Highwire Press

Drupalcon SF 2010