Syndicate

Feed

Theming the contact form in Drupal 6

The following exercise consists in theming the contact form in Drupal 6. Once we're done, it will look like we'll have created two fall-back-on 'pages' for contact forms, one page/form to 'request a quote' at Randy.com/contact/quote, and an other page/form for general inquiries at Randy.com/contact/info. The trick here — if there's one — consists in theming the contact form differently based on the requested URI.

This exercise will show you how to:

  • recognize when a module has not registered a specific theming function for a form it generates,
  • register a theming function for a Drupal form in your theme, using HOOK_theme,
  • work with template suggestions,
  • use the Devel module function dsm() to inspect complex variables, such as Drupal forms.

Using the contact module

Context: You're having a bad day. Your ex-boyfriend wrote to you that he loves going down on women, while he never went down on you ever for the entire duration of the relationship, and you're developing yet another Drupal site for Randy.com.

Randy believes he can give something back to the world. Doesn't everyone? On his new site, he wants to provide menu links to two different contact forms. One to 'request a quote', and another to ask Randy questions on whatever-else, like 'Randy, what is it you use to have such great hair?'.

You're in luck because in core there's a module to whip out a contact form — and validate it and send you its submitted content by e-mail. This module produces only one contact form, with a 'select an option' drop-down list of 'categories'. For each of these categories, you, the admin, can specify a different e-mail as the destination for the form's submitted content.

After some theming magic, it will look like you'll have created two different forms. But you won't... ah ah ah... After your magic touch, it will look like you'll have created two Drupal pages... but in truth (shhhh...) you definitely won't. Your site will behave as if, and that's all that matters.

To check if your contact module is enabled, try and access the page admin/build/contact/add, or look for 'Contact form' under 'Site building' in your Admin menu. If you land on the 'Site building' page, you have to enable the module. Head over admin/build/modules.

Enable the Contact module on your Modules page.

Now, you'll create two Contact categories, one for requesting a quote and an other for general inquiries. Head over admin/build/contact/add. Or, go to Site building → Contact form, then click on the Add category tab. Create a 'request a quote' category like so:

Adding a 'request a quote' category to your Contact form.

When you're done, you'll be redirected to admin/build/contact. Place your cursor over the 'edit' link next to your category. This link will point to admin/build/contact/edit/x. That number x is the cid of your category (contact hi-dee). Take mental note of it.

Determining the cid of a contact category.

And taking note of it.

Create a 2nd category for general inquiries, with its own e-mail address, etc. You will end up with two categories. If you had created categories before and deleted them, the 'cid' counter has never been reset (it never is), so you may add categories with, e.g., cid 32 and 33 (or 65 and 66). That's fine.

Both categories on the admin/build/contact page.

Take note of the 'cid' for general inquiries.

Taking note of the cid of the general inquiries category.

What theming function to override...?

So, now, you want to theme the contact form. That's your goal. The first question you ask yourself is What theming function must I intercept and override?

To find answers to these types of questions, 11heavens.com recommends using the module Theme Developer.

Download the Devel set of modules from Drupal.org, extract it to sites/all/modules, and enable the Devel module on your admin/build/modules page.

The devel module must be enabled.

Make sure to move the Development block to some active region on your page.

The block must be placed in some active region on the page, such as the Right sidebar.

Once you've landed on the contact page, click on the 'Enable Theme developer' link in your Development block.

The devel block should be visible to you now.

Now, enable the Themer info widget. The Themer info toggle checkbox sits on the bottom-left of your viewport. (Thought I should throw the fancy word "viewport" in there.)

The themer info toggle sits on the bottom-left of your viewport.

You want to click on the form, but you end up selecting one field or another within the form. Argh. Solution: click somewhere to the right of the Send e-mail button.

Select the contact form by clicking to the right of its submit button.

There's no specific theming function that's been registered to theme the contact form. How do you know that?

The function theme_form() is used to theme the form.

How do you know that theme_form() is used to theme the contact form? The Themer info widget tells you that. Take a look.

The all-purpose theme_form function is used to theme the contact form.

See it? Function called: theme_form().

You're not interested in overriding the theme_form() function. You want to theme the contact form. You do not want to theme all forms on Randy.com. You want to theme only that particular form. What will you do ?

First, eat something.

You will register a theming function for the contact form.

Modules and themes can both register theming functions. Modules and themes can both implement the function hook_theme.

Registering a theming function

To register a theming function for a form, you must know its Form ID.

There are at least two ways to find the Form ID.

  • Look at the <form> CSS id. That id is the hyphenated version of the Form ID: <form action="/contact" accept-charset="UTF-8" method="post" id="contact-mail-page">. Hence, the Form ID, here, is contact_mail_page.
  • Look at some hidden input in the form markup with name "form_id". The value of that input is the Form ID: <input name="form_id" id="edit-contact-mail-page" value="contact_mail_page" type="hidden">

You take mental note of the Form ID. (You and I both have amazing storage capacities in our brains.)

The Form ID of the contact form.

Just for fun (and to learn a few things), you take a peek inside the contact module to make sure that it provides no theming function for the 'contact_mail_page' form. You're looking for the module's implementation of hook_theme(). In the modules/contact folder, you search for the string function contact_theme.

And you find nothing.

The module is registering no theming function. Most core modules register their share of theming functions. The contact module is quite 'exceptional' in that regard.

For the sake of learning something about HOOK_theme, you'll look at another module's implementation of the hook. Open the file modules/user/user.module and look at lines 30-78.

<?php
/**
* Implementation of hook_theme().
*/
function user_theme() {
  return array(
   
'user_picture' => array(
     
'arguments' => array('account' => NULL),
     
'template' => 'user-picture',
    ),
   
'user_profile' => array(
     
'arguments' => array('account' => NULL),
     
'template' => 'user-profile',
     
'file' => 'user.pages.inc',
    ),
   
'user_profile_category' => array(
     
'arguments' => array('element' => NULL),
     
'template' => 'user-profile-category',
     
'file' => 'user.pages.inc',
    ),
   
'user_profile_item' => array(
     
'arguments' => array('element' => NULL),
     
'template' => 'user-profile-item',
     
'file' => 'user.pages.inc',
    ),
   
'user_list' => array(
     
'arguments' => array('users' => NULL, 'title' => NULL),
    ),
   
'user_admin_perm' => array(
     
'arguments' => array('form' => NULL),
     
'file' => 'user.admin.inc',
    ),
   
'user_admin_new_role' => array(
     
'arguments' => array('form' => NULL),
     
'file' => 'user.admin.inc',
    ),
   
'user_admin_account' => array(
     
'arguments' => array('form' => NULL),
     
'file' => 'user.admin.inc',
    ),
   
'user_filter_form' => array(
     
'arguments' => array('form' => NULL),
     
'file' => 'user.admin.inc',
    ),
   
'user_filters' => array(
     
'arguments' => array('form' => NULL),
     
'file' => 'user.admin.inc',
    ),
   
'user_signature' => array(
     
'arguments' => array('signature' => NULL),
    ),
  );
}
?>

You make these fine observations:

  • When the module provides a theming function per se, a 'file' is specified, the file in which the function is defined — but the module does so only when the function is stored in a separate file, that is, not in user.module.
  • Whenever the module provides a *.tpl.php file, a 'template' is specified. The template name, as the convention goes for a form, is the hyphenated version of the Form ID. You take mental note of this.

In the hook_theme() function, the key to each sub-array is the internal name of the hook. And we mean hook in "themespeak" (theme parlance), not "modulespeak". For a form, the name of the hook is the Form ID. Each sub-array contains info about said hook, and that info is an associative array. Among the info, there's only one required item to provide, and that is the 'arguments'. Plural form.

So, now, you'll register a theming function for the contact_mail_page form. The contact module hasn't done so. So, you will.

Registering a theming function for a form

You open your theme's template.php file. Randy.com uses the Garland theme. (For the money he's paying you, he better not fuss.) Here's the code to register a theming function for the 'contact_mail_page' form:

<?php
/* Register some theme functions for forms, theme functions
* that have not been registered by the module that created
* these forms...
*/
function garland_theme(){
  return array(
   
'contact_mail_page' => array(
     
'arguments' => array('form' => NULL),
     
'template' => 'contact-mail-page',
    ),
  );
}
?>

You take good note of the following:

  • The hook_theme function always returns an array. It returns an array even when it registers only one single theming function.
  • The hook_theme function returns an associative array. In that array, the key(s) is(are) the hooks (in "themespeak") that need to be themed — and for a form, the hook is the Form ID.
  • The 'arguments' element (plural form, please!) is also an associative array, and it is an array even when it contains only one element. The key/value pair for that one element, in our case, is 'form' and NULL.
  • You could provide an 'arguments' element, and stop there. You could then use a function like garland_contact_mail_page($form) to theme the form. But if you prefer to use a *.tpl.php file to theme the form, you need to provide a 'template' name. The value of 'template' is the name of the file you intend on using — without the tpl.php extension. Note that the template name can be ANYTHING AT ALL, that is, 'anything-at-all', provided you create a file that's called anything-at-all.tpl.php. But to follow Drupal's coding conventions, you, my Drupal-worker friend, use the hyphenated version of the Form ID.

Providing a template file and its preprocess function

When you want to override a template provided by a module, the process is the same, always. You locate the template file and copy it to your theme folder. When you want to pass additional variables to the template, you define a preprocess function for the template in your theme. That second step, however, is optional. It's usually not needed.

In your case, the module contact has provided no default template. Heck, it has not even provided a theming function! You had to register one! So you must go through these two steps:

  • Create the template file out of thin air.
  • Define a preprocess function to pass along to the template file any variable that it needs; all of the variables that have not been passed along already through the 'arguments' defined above; all of the variables it needs except the ones passed along already; all of the variables it needs excluding those variables that are automatically made available to all template files, like $user and $logged_in. (For a list of what's available in ALL *.tpl.php file, head over here).

By the way, are you still using global $user; in your template files? Just use $user, it's a default baseline variable available in all *.tpl.php files. It's part of your free meal.

For each template file used to theme content in Drupal 6, there must exist a function to pass to it whatever variables are needed to output content. For each template file used to theme content in Drupal 6, there exists somewhere a preprocess function.

Let's move on.

Create a file called contact-mail-page.tpl.php in your theme folder. In it, place this markup:

<p>So you would like to contact us...</p>
<div class="submit-contact-form"><?php print $form_markup; ?></div>

Then, create the skeleton for the template preprocess function in your template.php file like so:

<?php
function garland_preprocess_contact_mail_page(&$vars) {
 
$vars['form_markup'] = drupal_render($vars['form']);
}
?>

The function garland_preprocess_contact_mail_page(), as defined above, creates a new variable and passes it along to the template file. That new variable is $form_markup, and it contains the markup for the form 'contact_mail_page'.

You know this already about $vars:

  • In $vars, the key $vars['form'] is set, as well as the keys for the default baseline variables available to all templates.
  • $vars['form'] is a complex nested array that contains our 'contact_mail_page' form.
  • $vars is passed to the preprocess function 'by reference'. This means you can add to it, edit it, do whatever you want to it, and your changes will 'keep'. The preprocess function is usually used to pass additional variables to the template file — by adding to $vars — but it can also be used to change the value of stuff that's being passed already. You'll use it that way.

You know this already about drupal_render():

  • The drupal_render() function is what's used by themes to whip a $form array into XHTML markup.
  • The drupal_render() function can render any part of a form, or the whole thing.
  • When used to render the entire form, drupal_render() will only render these parts of the form that have not been rendered yet. It can keep track of what has been rendered thus far, in other words.

Clear your Drupal cache. That will clear the Theme registry.

Using the devel block to clear the cache.

Does Drupal use my template?

After you've refreshed your page in your browser, you should see a 'So you would like to contact us...' greeting between the title of the page and the contact form itself. The top of the contact page should look like this:

A test to see if our new template file is used.

Satisfied and relieved, you remove the 'So you would...' paragraph from the template file.

A little planning

So you want to produce two 'fake' Drupal pages, each one displaying a different version of the contact form, a version tailored to suit the context of a quote request or a general inquiry. You will create 2 files, one for each 'variation'. You'll name the first template file 'contact-mail-page-quote.tpl.php' and the second one 'contact-mail-page-info.tpl.php'. Make two copies of your contact-mail-page.tpl.php template, you'll work from there. Modify the names of these copies, and change their content.

You now have three 'extra' templates in your theme:

Three new template files in your theme folder.

Edit the content of contact-mail-page-quote.tpl.php to:

<p>You would like to request a quote...</p>
<div class="contact-for-quote"><?php print $form_markup; ?></div>

Edit the content of contact-mail-page-info.tpl.php to:

<p>You have an inquiry...</p>
<div class="contact-for-general-inquiry"><?php print $form_markup; ?></div>

Only contact-mail-page.tpl.php is 'seen', so far, by the theming engine. You'll change that.

Checking out my path

In the URI http://Randy.com/contact/quote, 'contact/quote' is $_GET['q']. Although it is a path that leads to nowhere, it is still something you can read. To read what's contained in this q value, you will use the Drupal function arg().This function breaks down the Drupal path into its components. Components of the path are separated by a forward-slash. For the URI http://Randy.com/contact/quote, arg(0) is the 1st component, ie: 'contact', and arg(1) is the 2nd component, ie: 'quote'. You will read the second component, and will suggest a template file based on it.

<?php
function garland_preprocess_contact_mail_page(&$vars) {
 
// If the visitor types contact/quote or contact/info
 
switch(arg(1)) {
    case
'quote' :
     
$vars['template_file'] = 'contact-mail-page-quote';
      break;
    case
'info' :
     
$vars['template_file'] = 'contact-mail-page-info';
  }
 
$vars['form_markup'] = drupal_render($vars['form']);
}
?>

You are suggesting one template file in each case, hence you use the variable $vars['template_file'] instead of $vars['template_files']. You assign to that 'suggestion' variable the name of the dot-tpl.php file you plan on using minus the extension. Just like you did in your implementation of HOOK_theme when you told the theming engine to use 'contact-mail-page', which effectively points to the file contact-mail-page.tpl.php.

Now, you're thinking about how you want to render or display the contact form for each situation, the 'quote' situation and the 'info' one. In both situations, you do NOT want to show the drop down selection for categories. A category is picked based on the requested URI. The URI already informs us as to what category was chosen, in other words. So you'll hide that element in the form in both cases. For that you'll use CSS display:none. However, when the form is submitted, that information about the cid will be missing, so you must 'fill in' that form field behind the scenes, on behalf of the visitor. This is something that you can do in your theme.

The values you'll assign here to $vars['form']['cid']['#value'] are the Contact Hi-dee you had 'memorized' previously. So they may not be 1 and 2.

<?php
function garland_preprocess_contact_mail_page(&$vars) {
 
/* If the visitor types contact/quote or contact/info,
      otherwise the default template is used, ie: contact-mail-page */
 
switch(arg(1)) {
    case
'quote' :
     
$vars['form']['cid']['#value'] = 1;
     
$vars['form']['cid']['#prefix'] = '<div style="display:none;">';
     
$vars['form']['cid']['#suffix'] = '</div>';
     
$vars['template_file'] = 'contact-mail-page-quote';
      break;
    case
'info' :
     
$vars['form']['cid']['#value'] = 2;
     
$vars['form']['cid']['#prefix'] = '<div style="display:none;">';
     
$vars['form']['cid']['#suffix'] = '</div>';
     
$vars['template_file'] = 'contact-mail-page-info';
  }
 
$vars['form_markup'] = drupal_render($vars['form']);
}
?>

Clear your Drupal cache, to clear the Theme registry.

Using the devel block to clear the cache.

Then, head over to the 'contact/quote' page. What do you see? Test the 'contact/info' page as well.

Checking out the form

To troubleshoot any problems you may encounter while editing your form, you need to inspect it. The devel module provides a kit of 5 functions to inspect the content of variables:

Function To inspect content of Content shown Using a recursive function
dsm() Any variable In a message Yes, perfect for all.
dvm() A complex variable, such as an object or array In a message No. Ugly print.
dpr() A complex variable, such as an object or array At the top of the page. Yes. Perfect for complex, ie: nested, objects and arrays.
dpm() A complex variable, such as an object or array In a message. Yes. Perfect for complex, ie: nested, objects and arrays.
dvr() Any variable At the top of a page. No. Ugly print.

To inspect complex nested arrays such as forms, you can use either dpr() or dpm()/dsm(). These functions pretty-print forms either at the top of the page, or in the 'message' area. dpm() and dsm() have the same behavior.

In the 6.x-1.8 version of the Devel modules, dsm and dpm print the content of variables at the top of the page. That's a bug. The m in dsm and dpm means 'message' — it uses the function drupal_set_message().

To make sure you set the value for cid properly in your theme preprocess function, you can check the content of the $vars['form']['cid'] field within your code, like so:

<?php
  $vars
['form']['cid']['#value'] = 2;
  ...
 
$vars['form']['cid']['#value'] = 1;
  ...
 
// Checking the content of the cid field
 
dsm($vars['form']['cid']);
  ...
?>

Changing field labels

You now want to modify the field labels in the contact form.

<?php
function garland_preprocess_contact_mail_page(&$vars) {
 
/* If the visitor types contact/quote or contact/info,
      otherwise the default template is used, ie: contact-mail-page */
 
switch(arg(1)) {
    case
'quote' :
     
$vars['form']['contact_information']['#value'] = t('We will get back to you with a quote within 48 hours.');
     
$vars['form']['message']['#title'] = t('What you need');
     
$vars['form']['subject']['#title'] = t('Name of your company');
     
$vars['form']['cid']['#value'] = 1;
     
$vars['form']['cid']['#prefix'] = '<div style="display:none;">';
     
$vars['form']['cid']['#suffix'] = '</div>';
     
$vars['template_file'] = 'contact-mail-page-quote';
      break;
    case
'info' :
     
$vars['form']['contact_information']['#value'] = t('Send us your question. A real person will get back to you very shortly.');
     
$vars['form']['message']['#title'] = t('Your question');
     
$vars['form']['cid']['#value'] = 2;
     
$vars['form']['cid']['#prefix'] = '<div style="display:none;">';
     
$vars['form']['cid']['#suffix'] = '</div>';
     
$vars['template_file'] = 'contact-mail-page-info';
  }
 
// dsm($vars['form']);
 
$vars['form_markup'] = drupal_render($vars['form']);
}
?>

Now head over to the 'contact/quote' page. (Also look at the other version of the contact form, at 'contact/info'.)

The contact form to request a quote.

Is using template files such a great idea..?

You ask yourself: Why in the hell did I create template files when all they do, essentially, is print a variable? They barely contain any HTML markup! All contact-mail-page-info.tpl.php contains, for example, is this:

<p>You have an inquiry...</p>
<div class="contact-for-general-inquiry"><?php print $form_markup; ?></div>

The paragraph that contains 'You have an inquiry...' can certainly be added to the form as a markup field. At the theme layer, one can still add additional fields to a form, even though they will not be validated, neither submitted. They will be added just for the 'show'. A paragraph is just for show. You can also wrap the form within a div with a specific class name, using $vars['form']['#prefix'] and $vars['form']['#suffix']. That way, you can take care of supplying the form with a CSS class name of contact-for-general-inquiry.

So, you've changed your mind about all this. You delete the three template files. All three. (You heard that using a template is five times slower than using a theming function.)

Then, you open your template.php file. You remove from the garland_theme() function any mention of the template file 'contact-mail-page.tpl.php'. The function becomes:

<?php
function garland_theme(){
  return array(
   
'contact_mail_page' => array(
     
'arguments' => array('form' => NULL),
     
// 'template' => 'contact-mail-page',
   
),
  );
}
?>

Then, you go ahead and comment out the whole preprocess function. Preprocess functions are for template files only.

Next, you write your theming function. This consists in moving the code from the commented-out preprocess function to a new function you call garland_contact_mail_page($form). In that new function, you replace all mention of $vars['form'] with $form. A 'find and replace' tool will be quite useful here. What the theming function must return is themed content. Hence, you delete the line of code which consisted in adding a variable to $vars, and place the following 'return' instruction in its place:

<?php
// returning the themed form
return drupal_render($form);
?>

Here's the code for the entire function:

<?php
function garland_contact_mail_page($form) {
 
/* If the visitor types contact/quote or contact/info,
      otherwise the default template is used, ie: contact-mail-page */
 
switch(arg(1)) {
    case
'quote' :
     
$form['intro']['#value'] = '<p>' . t('You would like to request a quote...') . '</p>';
     
$form['intro']['#weight'] = -40;
     
$form['contact_information']['#value'] = t('We will get back to you with a quote within 48 hours.');
     
$form['message']['#title'] = t('What you need');
     
$form['subject']['#title'] = t('Name of your company');
     
$form['cid']['#value'] = 1;
     
$form['cid']['#prefix'] = '<div style="display:none;">';
     
$form['cid']['#suffix'] = '</div>';
     
$form['#prefix'] = '<div class="contact-for-quote">';
     
$form['#suffix'] = '</div>';
      break;
    case
'info' :
     
$form['intro']['#value'] = '<p>' . t('You have an inquiry...') . '</p>';
     
$form['intro']['#weight'] = -40;
     
$form['contact_information']['#value'] = t('Send us your question. A real person will get back to you very shortly.');
     
$form['message']['#title'] = t('Your question');
     
$form['cid']['#value'] = 2;
     
$form['cid']['#prefix'] = '<div style="display:none;">';
     
$form['cid']['#suffix'] = '</div>';
     
$form['#prefix'] = '<div class="contact-for-general-inquiry">';
     
$form['#suffix'] = '</div>';
  }
 
// dsm($form);
 
return drupal_render($form);
}
?>

Even with a '#weight' of -40 applied to the added 'intro' markup field, it still sinks to the bottom of the form. You can use drupal_render() to make sure that the intro text always appears on top, like so:

<?php
function garland_contact_mail_page($form) {
 
/* If the visitor types contact/quote or contact/info,
      otherwise the default template is used, ie: contact-mail-page */
 
switch(arg(1)) {
    case
'quote' :
     
$form['intro']['#value'] = '<p>' . t('You would like to request a quote...') . '</p>';
     
$form['contact_information']['#value'] = t('We will get back to you with a quote within 48 hours.');
     
$form['message']['#title'] = t('What you need');
     
$form['subject']['#title'] = t('Name of your company');
     
$form['cid']['#value'] = 1;
     
$form['cid']['#prefix'] = '<div style="display:none;">';
     
$form['cid']['#suffix'] = '</div>';
     
$form['#prefix'] = '<div class="contact-for-quote">';
     
$form['#suffix'] = '</div>';
     
$output = drupal_render($form['intro']);
      break;
    case
'info' :
     
$form['intro']['#value'] = '<p>' . t('You have an inquiry...') . '</p>';
     
$form['contact_information']['#value'] = t('Send us your question. A real person will get back to you very shortly.');
     
$form['message']['#title'] = t('Your question');
     
$form['cid']['#value'] = 2;
     
$form['cid']['#prefix'] = '<div style="display:none;">';
     
$form['cid']['#suffix'] = '</div>';
     
$form['#prefix'] = '<div class="contact-for-general-inquiry">';
     
$form['#suffix'] = '</div>';
     
$output = drupal_render($form['intro']);
  } 
 
// dsm($form);
 
return $output . drupal_render($form);
}
?>

This is a contrived example of course. This is an exercise. You may wonder why in the hell you ought to add some 'intro' text field when the form already contains a 'contact_information' field. I am doing this just to show you how to add a field to a form 'for show'.

To place text in a form is not exactly adding a field to it. But still, markup that just sits there (to be read) is a field in its own right and it is the default type for a form field in the Drupal form API. Hence, you need not specify a '#type' of 'markup' for the new $form['intro'] field.

Don't forget to clear the cache.

Now head over to your 'contact/quote' page.

The contact form to request a quote.

All you have left to accomplish now is go to admin/build/menu and add menu items that link to contact/quote and contact/info, and then add these menu items to a menu, e.g. your primary links. And you're done.

Randy is happy. He sends you two-thirds of the money he promised you, along with this photo of him. He says he's on the left.

Picture of Randy

[]

This tutorial was helpful to you? Post a comment and/or donate. Thank you.

Last edited by Caroline Schnapp about 2 weeks ago.

Comments

5 times slower?

(You heard that using a template is five times slower than using a theming function.)

Really.. 5 times slower.. It couldn't be that bad
[Or did you just want to show an alternate method].

Fantastic write up!

Regards
Alan

An alternate method

Really.. 5 times slower...

merlinofchaos said so during a screencast presentation on theming Drupal 6 theming: an overview of the changes to theming in Drupal 6 for which you can find the slides here.

Even if it'd be five times slower it would still be very quick, but that 'slowness' is the reason why Drupal will never provide templates to theme these 'hooks' that you can find by the dozens on one page. For example, you will not find a template to theme 'menu_item_link' or 'menu_item' or 'links' or 'more_link'. The rumors have it that Drupal core will provide even more templates, to copy and drop in your theme, for Drupal 7, but it won't provide templates for these hooks for sure.

But you're absolutely correct. Although I personally prefer using a function here over a template file, I _really_ wanted to show both ways. One may not perceive any difference in speed here, at all. Generally, I prefer to use a function when all that the template does is print the content of a single variable.

Fantastic write up!

Thank you :-)

Webforms

I thought I should mention in passing that there's a contributed module to create forms as nodes. After enabling the module, you have one more content type at your disposal, webform. Each time you create a 'webform', you end up with one node = one form. Each form has the attributes of forms as we know and love them, it has its own Form ID, so you can theme it, etc. And the webform is displayed in a node, so it has the attributes — and ADVANTAGES of nodes.

While you create a webform, you're presented with an E-mail to address field, so that submitted content for that form will always be sent to that an e-mail address, just like for the contact form.

You can show two of these nodes/forms on the same page like Laurence does on his contact page. Check it out: http://lhmdesign.com/contact.

The webform module has a beta version for Drupal 6, so it's all lookin' and lickin' good for its lifespan. The project page for that module is at http://drupal.org/project/webform. This module is also good for questionnaires, etc.

In about 5 minutes, I was able to install the module and create this webform:

Tell me your fantasy form.

I can enable/disable comments for it, publish/unpublish it, remove its stickiness ;-)

webform

Just tried webform as I'm busy with a site where different people have to fill in forms and it gets sent to different people... or posted to different nodes hopefully when I'm done...

Really usefull module if your not in the mood to setup CCK fields and a entire new content type for each little thing.

I'm sure you could even use this as a nice contact module replacement.

Nice contact module replacement

I'm sure you could even use this as a nice contact module replacement.

Certainly!

That's how I discovered the existence of the module. I was on a Drupal site, filling a contact form... there were 2 different contact forms on the same page, and that picked my curiousity, so I did a View Source. I saw 'webform' in the class names, and googled that.

hook_theme() is only called when cache is cleared

Just to clarify -- the garland_theme() function goes in the module's .module file, not the theme's template.php file. ALSO, this function is only called when the cache is rebuilt (so after you click "Clear Cache" from the Devel sidebar) -- it does NOT get called upon each page load. This was driving me crazy and I couldn't figure it out until I ran through everything with a step debugger.

Thanks for the great article -- this is the only place on the entire web (AND in all the drupal books) that I found an explanation for rendering a form with a template. Whatever extra load time results from doing it this way is worth it to me, because we have web designers who create HTML and hand it off to programmers -- trying to put that HTML into a php function would be a nightmare.

Thank you

Just to clarify -- the garland_theme() function goes in the module's .module file, not the theme's template.php file.

Not at all. We're not writing a module here — and you certainly should not touch core. The garland_theme() function goes in the theme's template.php file.

ALSO, this function is only called when the cache is rebuilt (so after you click "Clear Cache" from the Devel sidebar)

The tutorial is clear about this: after registering the theming function and creating the template for the contact form, I say:

Clear your Drupal cache. That will clear the Theme registry.

I even provide a screenshot.

And when I do not use a template but an overriding theming function, I still say:

Don't forget to clear the cache.

garland_theme() misprint?

I love your articles. Thank you!

Instead of returning array('user_register' => array())..., I believe the final garland_theme() function should look like:

function garland_theme(){
  return array(
    'contact_mail_page' => array(
      'arguments' => array('form' => NULL),
      // 'template' => 'contact-mail-page',
    ),
  );
}

Note: After 10 attempts to format this comment with <code> and/or <?php tags, none of my Previews looked 'pretty.' Sorry if it posts that way, too.

[EDIT by Caro] I changed the filter here, so code is tabbed. I will reply in another message...

Thank you, Rodney

I edited the tutorial.

I am so sorry about the filter... argh. I will fix this.

Comment form

I would love to see something like this for the comment form.

I tried a similar approach with the comment form, following this and the Drupaldojo video and it for some reason nothing worked.

I can call another template but cant seem to output any variables in it (it does get called and I can print a simple bit of html and message, but all the form fields fail to render). dsm outputs nothing, drupal_render($form); outputs nothing. When I use the devel theme developer it gives me no candidates for functions.

Is the comment form somehow different? how can I theme the comment for radically different than the default? I want to re-order all the fields and use a lot of additional markup.

Thanks for the great tutorial - I just wish this would work for the comment form also. I've searched high and low for advice, but no one seems to have posted any info on theming the comment for in this way as yet.

Theming and CCK forms

Hi Caroline,

Thanks for this great tutorial.

I tried your method on a CCK form and it doesn't seem to be working - cleared cached data and everything. What I did: First, I copied the garland theme to all/themes folder and renamed it myGarland - then I carried out the changes. My CCK is called front_page, so the form_id wad front_page_node_form. So I had the following in my template.php file:

function myGarland_theme() {
  return array(
    'front_page_node_form' => array(
      'arguments' => array('form => NULL'),
      'template' => 'front-page-node-form',
    ),
  );
}
function myGarland_preprocess_front_page_node_form(&$vars) {
  $vars['form']['title']['#title'] = t('My title');
  //dsm($vars['form']);
  $vars['form_markup'] = drupal_render($vars['form']);
}

and created a file called: front-page-node-form.tpl.php

Is there something else I need to do to get it to work on CCK forms?

Best regards, Remy

Questions

A few questions:

  1. Did you make the myGarland theme your new default theme?
  2. Did you guess what the form ID was, or did you check the source..?
  3. What does Themer info say now...? What function or template is used now to theme the form?
  4. I suppose uncommenting the dsm function call does not result in the form being output to screen?

Thanks for the feedback, Remy. Very appreciated!

Theming CCK forms

Hi,

Please see my responses to your questions below:

1) Yes - I did. Went through your tutorial for the contact form and that worked fine using both methods outlined.
2) Checked the form id using your method (i.e. from )
3) Themer info actually showed myGarland_front_page_node_form as candidate function names.
4) And no, uncommenting dsm function call didn't work

Hope you can help - I am about at my wits end. In case it helps, I've also logged an issue on drupal: http://drupal.org/node/316672.

regards, remy.

Themer info actually showed

Themer info actually showed myGarland_front_page_node_form as candidate function names.

Function name? It should precisely give you the name of a template to be used.

Here's what the themer info says:

Parents: theme_form < page.tpl.php
Function called:
theme_node_form()
Candidate function names:
myGarland_front_page_node_form < phptemplate_front_page_node_form < theme_front_page_node_form etc....

I looked at your support request

The code is wrong in there. You use:

function myGarland_theme() {
  return array(
    'contact_mail_page' => array(
      'arguments' => array('form' => NULL),
      //'template' => 'contact-mail-page',
      ),
  );
  return array(
    'front_page_node_form' => array(
      'arguments' => array('form' => NULL),
     //'template' => 'front-page-node-form',
    ),
  );
}

With your code you are never registering a theming function, or template, for your CCK form. Try this:

function myGarland_theme() {
  return array(
    'contact_mail_page' => array(
      'arguments' => array('form' => NULL),
      'template' => 'contact-mail-page',
    ),
    'front_page_node_form' => array(
      'arguments' => array('form' => NULL),
      'template' => 'front-page-node-form',
    ),
  );
}

Off-topic

You should perhaps avoid using capital letters in names of folders/files (and PHP function names). Bad practice that may also cause problems with Apache along the way.

found that error ... corrected it

Sorry, should have mentioned that I found that error and corrected it - didn't make any difference, unfortunately (I was very excited when I found the error - thought my troubles were over!)

Do you think perhaps using capital letter 'G' is the problem?

Not sure

Do you think perhaps using capital letter 'G' is the problem?

I don't think so.

I wish I could be more helpful.

Can you show me a screen capture of what Themer info shows for the node form? You can upload the screen capture to the Drupal support request issue.

Screenshot of theming info uploaded

Hi Caroline,

I've uploaded the screenshot of my theming info to the support issue on Drupal - http://drupal.org/node/316672. Hope you can help.

Thanks, Remy

Thanks alot

Catching up on differences of Drupal 6 and 5 for a job interview tomorrow when I came across your site and this write up. The write up is excellent both in technical clarity and entertainment. Your content and banter remind me of a more technical Annalee Newitz.

Thank you, evoltech

Your content and banter remind me of a more technical Annalee Newitz.

That is a great, great compliment.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <css> <html> <javascript> <mysql> <php> <span> <a> <b> <i> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <sup> <sub> <dd> <del> <blockquote> <img> <q> <p> <div>
  • Lines and paragraphs break automatically.
  • You can enable syntax highlighting of source code with the following tags: <css>, <html>, <javascript>, <mysql>, <php>.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
1 + 2 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.