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\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 .= " \n"; $admin .= form_render($form['author']); $admin .= " \n"; } if (isset($form['options'])) { $admin .= " \n"; $admin .= form_render($form['options']); $admin .= " \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 .= " \n"; $output .= form_render($form); $output .= " \n"; if (!empty($admin)) { $output .= " \n"; $output .= $admin; $output .= " \n"; } $output .= $buttons; $output .= "\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\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 .= " \n"; $admin .= form_render($form['author']); $admin .= " \n"; } if (isset($form['options'])) { $admin .= " \n"; $admin .= form_render($form['options']); $admin .= " \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 .= " \n"; $output .= form_render($form); $output .= " \n"; if (!empty($admin)) { $output .= " \n"; $output .= $admin; $output .= " \n"; } $output .= $buttons; $output .= "\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.