Overriding Themeable Functions: The Where's, the Why's, and the How's

There's good news and bad news. First, the good news: overriding theme functions is easy. The bad news: every theme function is different, and there isn't a standard proceedure of going about it; if you don't know what you are doing, its quite easy to accidently do something ugly, or foolish.

So, in the next few tutorials, we are going to explore the hows, whys, why nots, and what ifs of overriding theme functions. Each of these functions will present a different set of challenges, and opprotunities to do something stupid, etc. Today's lesson is "Building a better node form". In this tutorial you will learn

  1. Where to look for a file containing the themablefunction
  2. How to find it in the code
  3. How to construct the phptemplate callback
  4. How to construct the tpl.php file

A better node form

If you were a node form, where would you hide? Its a good question, and the sort of question you'll find yourself asking quite a lot as you begin to become more experienced in PHPtemplate. So let's actually take a moment to learn how to ask these kinds of questions.

Finding the bastard

Question 1: Are you overriding something that appears everywhere (e.g. menu items), or something that is clearly generated by one module (e.g. a comment).

Generally, if you are dealing with something used by nearly every module, you'll find your function in the "includes" directory. In contrast, (and this is not exactly a "twist"), you'll find module specific functions in the module themselves. In conclusion, the answer to our inquiry is: " we're probably going to find our function in node.module."

Question 2: What function are you overriding?

When facing questions like these, its nice to remember why god invented CTRL-F (find text). Get used to CTRL-F, it is your friend. In the node.module, start with a search for "theme_".

Indeed, it doesn't take long for you to run into function theme_node_form($form); Let's take a look at the code in its entirety:

<?php theme_node_form($form) {  $output = "\n<div class=\"node-form\">\n";  if (isset($form['node_preview'])) {    $output .= form_render($form['node_preview']);  }  // Admin form fields and submit buttons must be rendered first, because  // they need to go to the bottom of the form, and so should not be part of  // the catch-all call to form_render().  $admin = '';  if (isset($form['author'])) {    $admin .= "    <div class=\"authored\">\n";    $admin .= form_render($form['author']);    $admin .= "    </div>\n";  }  if (isset($form['options'])) {    $admin .= "    <div class=\"options\">\n";    $admin .= form_render($form['options']);    $admin .= "    </div>\n";  }  $buttons = form_render($form['preview']);  $buttons .= form_render($form['submit']);  $buttons .= isset($form['delete']) ? form_render($form['delete']) : '';  // Everything else gets rendered here, and is displayed before the admin form  // field and the submit buttons.  $output .= "  <div class=\"standard\">\n";  $output .= form_render($form);  $output .= "  </div>\n";  if (!empty($admin)) {    $output .= "  <div class=\"admin\">\n";    $output .= $admin;    $output .= "  </div>\n";  }  $output .= $buttons;  $output .= "</div>\n";  return $output;}?>

Remember when you first started coding HTML? Remember how overwhelming a full page of HTML could look? How did you learn to get comfortable with HTML? Simple: you learned to read the documents structure, as opposed to line after line of code. So, in building our phptemplate_node_form() function, we should begin by asking ourselves specifically what we are and are not looking for:

What DOES NOT go in your phptemplate_hook_foo() function: OUTPUT, OUTPUT, OUTPUT

What DOES go in your phptemplate_hook_foo() function: VARIABLES, VARIABLES, VARIABLES

Scanning through the above code you will find that well over 95 percent of it is blatent output. In fact, if you delete every line of output in the function, all you are left with is this:

<?php theme_node_form($form) {//seriously, that's it.}?>

To override the function, theme_node_($form) becomes phptemplate_node_($form) in the template.php file.

TEMPLATE.PHP:

<?php  // simply replace "theme_" with "phptemplate". phptemplate_node_form($form) {}?>

This code, obviously, will do absolutely nothing. So how do we get the form into a tpl.php file? Simple: every overridden theme function must return _phptemplate_callback('["the name of the tpl.php file"]', array([the variables returned to the node form ]));

TEMPLATE.PHP

<?php //remember how we stripped the function of all output? function phptemplate_node_form($form) {return _phptemplate_callback('node_form', array('form' => $form)); }?>

Where did the $form variable come from? Its the only non output variable in the function silly!

So let's save the file at our live site, and see what happens:

Before

After

Whoa... We see nothing... That's because we forgot to create our node_form.tpl.php file, didn't we? That would probably explain why we see nothing in place of the node_form. How do we make that file? A good place to start is the function we are overriding. This time:

Find all output and paste it into your newly created node_form.tpl.php file.

<?php $output = "\n<div class=\"node-form\">\n";  if (isset($form['node_preview'])) {    $output .= form_render($form['node_preview']);  }  // Admin form fields and submit buttons must be rendered first, because  // they need to go to the bottom of the form, and so should not be part of  // the catch-all call to form_render().  $admin = '';  if (isset($form['author'])) {    $admin .= "    <div class=\"authored\">\n";    $admin .= form_render($form['author']);    $admin .= "    </div>\n";  }  if (isset($form['options'])) {    $admin .= "    <div class=\"options\">\n";    $admin .= form_render($form['options']);    $admin .= "    </div>\n";  }  $buttons = form_render($form['preview']);  $buttons .= form_render($form['submit']);  $buttons .= isset($form['delete']) ? form_render($form['delete']) : '';  // Everything else gets rendered here, and is displayed before the admin form  // field and the submit buttons.  $output .= "  <div class=\"standard\">\n";  $output .= form_render($form);  $output .= "  </div>\n";  if (!empty($admin)) {    $output .= "  <div class=\"admin\">\n";    $output .= $admin;    $output .= "  </div>\n";  }  $output .= $buttons;  $output .= "</div>\n";  return $output;?>

The code doesn't work does it? You forgot one final step. Its right above this paragraph "return $output" becomes "print $output;"

At this point I suggest that you do something with the overriden node($form). Maybe clean up the unnecessary $output .= variables, and building it into an HTML like template. But that's just me. I like to make things easy for myself.

Stay tuned for tomorrows lesson on dynamically adding CSS classes to menu, node, and comment links.

Comments

modify the add/edit story form to a 2 column layout

This is a great article.

I am using taxonomy for categories.
I wish to modify the add/edit story form to look something like: (2 column layout)

-------++
-------++
-------++
-------++
-------++
-------++

Basically, i want the category fields (i have four categories) to come to the right column and everything (other form fields) else to come in the left column.

Any suggestion as to how the node_form.tpl.php file should look in this case ?

Looking forward to your help.

Thanks,
Sammy

Thanks for the

Thanks for the tutorial.

Looking through the forums and documentation on drupal.org, I noticed that many code examples featuring themed functions do not use the _php_template_callback madness. Instead the themed function is just sitting in template.php.

Is this wrong? It seems a lot neater than having x number of files for each themed function.

Thanks

No, I actually don't use

No, I actually don't use tpl.php files in my work for this reason. In fact, I discourage my coworkers from overly relying on even template.php files... but that's a whole nuther story....

Generally speaking, a lot of the weirder characteristics of drupal templates are the result of an official priority of the them system, that being that it should be easier for "non programmers" to build, and alter themes. The wisdom of this priority among the core maintainers is debatable. Personally, I wish Drupal would take a quick shot of the Perl philosophy and seek to make it easier for experts to do hard things, as opposed to making it easier for novices to do easy things.

Of course, I'm biased as I program drupal websites 45 hours a week.

great content

Thank you for these tutorials ! They're all very helpfull.

The only thing i often miss is the location of the files you are talking about.
Please think about the Drupal Newbees who want to take the A train with your tut.

Should be great to write path_to_file/file.ext instead of directly file.ext,
since there are a lot of redundant files having the same name in different folder.

Just what I need (er, I think???)

Really nice, Nick. A few days ago I asked an HTML/CSS instructor in Apple's Soho NYC retail store about how to mod blog templates; he said it was barely easier than starting from scratch. I hope this is a start to a template mod course.

We finally have a working code base

To test the new split mode templates. Basically, instead of having the theme code be functions, they will already be templates, that you can just copy and edit. We can also finally write a proper inline theme editor =)

When tutorialing, you might want to.....

show both before and after, if you show before, or after. A screen shot which displays the before is not nearly as useful as a pair of screenshots which display both the before and after. That is, if you actually intend to educate.

Nah — I was in a hurry

Nah -- I was in a hurry yesterday and mismashed my screenshots. My intent is not to educate, but to confuse. :-)

So, that was a joke.

So, that was a joke. Apparently this was not apparent to everyone who read it :-/

Who are these clowns?

Who make sarcastic comments about your tutorials ie 'if you actually intend to educate'.

I'm sure you find suggestions helpful but it seems to me like some people assume you owe them something...

I'm finding your tutorials very useful, I hate to think of the confusion I would have suffered in their absence.

Is there a follow up to 'dominating the login form' coming any time soon?

Clowns indeed!

People should appreciate the time and effort to create these tutorials. They are free for crying out loud!
Very useful tutorials, well done!

+1 "for the dominating the login form"