Syndicate

Feed

Theming the register form in Drupal 6

The following exercise consists in adding Terms of Use to the register form in Drupal 6. Provided that your website visitors aren't logged in, they can view the register form on the page myWebSite.com/user/register.

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,
  • use the Devel module function dsm() to inspect complex variables, such as Drupal forms,
  • use the Theme Developer 'Themer info' widget to inspect those forms that only 'anonymous users' can see, for instance... the register form.

Storing the Terms of Use in a node

Context: You're having a bad fuel day, and you're developing a Drupal site for Randy.com. You were lucky enough to find ready-made Terms of Use, while googling +"Terms of Use", Terms of Use you'll shamelessly 'use' on Randy.com — yes, you will use these copyrighted Terms of Use, to protect Randy's website content.

The Terms of Use are terse just as they ought to be:

Terms of Use

By visiting or using Randy.com (also referred to as "Site", "WebSite"), created and operated by Randy Solutions (also referred to as "we", "us", "Randy.com"), you (also referred to as the "user", "visitor") agree to be legally bound by the following terms and conditions. If you do not agree to these terms, you should not use this Site.

We reserve the right, to update or revise these Terms of Use at any time without notice. Please check the Terms of Use periodically for changes. The revised terms will be effective immediately as soon as they are posted on the WebSite and by continuing to use the Site you agree to be bound by the revised terms.

Disclaimer

All materials, information, software, products and services, included in or available through this Site (also referred to as "content") are provided on an "as is" basis. The content is provided without warranties of any kind.

We do not warrant that the content on this WebSite is accurate, reliable, correct, entertaining or fun; that this Site will be available at any particular time or location; that any defects or errors will be corrected; or that the content is free of viruses or other harmful components. Your use of this Site is solely at your risk. Any material viewed, downloaded or otherwise obtained through the use of this Site is done solely at your own discretion and risk, and you will be solely responsible for any damage, including without limitation personal injury or distress, damage to your computer system, or loss of data, that results from the viewing or download of any such material.

Copyright

Any use, including the reproduction, modification, distribution, transmission, republication, display or performance, whether or not for profit or commercial gain, of any part of the content on the WebSite or any part of the website design, without prior written permission or authorization is strictly prohibited.

You want to show these Terms of Use in the register form, and you want to add a required checkbox below these Terms with something like this written next to it: I agree with these Terms.

Randy agrees to use these Terms of Use, but not "as is". He would like to insert, here and there, words to elicit a WTF-reaction. But he's not a programmer. So you've created a node to store the Terms, and given the node alias to Randy (that is, terms-of-use), so that Randy may change the Terms whenever he wants to whatever he wants (click on the edit tab, Randy).

The node is not promoted to the first page.

You've taken mental note of the node hi-dee.

The node id was written down on a post-it.

You'll need that number later. Randy only needs to know the path alias. You remind Randy that he probably should not promote the 'page'. (Leave the publishing options alone, Randy.)

What theming function to override...?

So, now, you want to theme the register 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.

You would like to use the Themer info widget on the register page, but, alas, when you try and access the register page you get an Access denied message: You are not authorized to access this page.. You're the admin, what the hell is that? The explaination: you can view the register form — on the page myWebSite.com/user/register — only if you aren't logged in. However, when you log out, you can no longer use the Themer info widget. What to do?

If you're working locally, and not on some site that sits on a server somewhere, you can give access to the Themer info widget to anonymous users. Head over to the admin/user/permissions page and select the appropriate radio button. Then, log out.

The block will be seen even by anonymous users.

You might also want to change the Devel module 'permissions' to make the Switch user block available to all logged-in users, that is, 'authenticated users'. When you're working on your local machine (in your bunker), and you're tesing who can see and do what, it's efficient to be able to switch from being logged-in as yourself, to being logged-in as some Dude with role X, to being logged-in as Dudesse with role Y, to being logged-in as yourself again, etc. and in a snap. Having to log out and login is tedious.

The Switch User block.

The Switch User permission.

End of sidenote. Back to business.

Once you've finally landed on the register page as an anonymous user, 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 form button(s). So, you click somewhere on the right of the Create a new account button.

Select the user register form by clicking to the left of its buttons.

There's no specific theming function that's been registered to theme the register form. How do you know that? Answer: the function theme_form() is used to theme the form. How do you know that theme_form() is used to theme the register form? The Themer info widget tells you that.

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

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

First, eat something.

You will register a theming function for the register 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="/user/register" accept-charset="UTF-8" method="post" id="user-register">. Hence, the Form ID, here, is user_register.
  • 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-user-register" value="user_register" 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 register form.

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

Search results guide you to the file user.module.

<?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),
    ),
  );
}
?>

The module is registering quite a lot of theming functions, but nah, none for 'user_register'.

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.

So that you understand, in the hook_theme() function, the key to each sub-array is the internal name of the hook. And we mean hook in "themespeak", 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, and that is the 'arguments'. Plural form.

So, now, you'll register a theming function for the user_register form. The user 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 'user_register' form:

<?php
/* Register some theme functions for forms, theme functions
* that have not been registered by the module that created
* them...
*/
function garland_theme(){
  return array(
   
'user_register' => array(
     
'arguments' => array('form' => NULL),
     
// and if I use a template file, ie: user-register.tpl.php
     
'template' => 'user-register',
    ),
  );
}
?>

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 "theme speak") 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_user_register($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. (And you prefer that. But why?) 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 we want to override a template provided by a module, the process is the same. Always. We locate the template file and copy it to our theme folder. When we want to pass additional variables to the template, we define a preprocess function for the template in our theme. The second step is optional. It's usually not needed.

In our case, the module has provided no default template. Heck, it has not even provided a theming function! We had to register one! So we 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 requires except the ones passed along already, and 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 any *.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 user-register.tpl.php in your theme folder. In it, place this markup:

<h2>Hello world</h2>
<!-- Let's print the form's XHTML -->
<?php print $form_markup; ?>

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

<?php
function garland_preprocess_user_register(&$vars) {
 
// That's where $form_markup is created
 
$vars['form_markup'] = drupal_render($vars['form']);
}
?>

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

You know this already about $vars:

  • The $vars includes the key $vars['form'] as well as the default baseline variables available to all templates.
  • $vars['form'] is a complex nested array that contains our 'register_form'.
  • $vars is passed to the preprocess function 'by reference'. This means we can add to it, edit it, do whatever we want to it, and our 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 alter stuff that's 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 'Hello world' greeting between the title of the page and the register form.

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

Satisfied and relieved, you remove the Hello World heading from your template file.

A little planning

You're thinking about how it is you want to include your Terms of Use. You want to create a new fieldset with title "Terms of Use". In that fieldset you want to print the text that's in node... what's the number? 402. Below the text you want to put some checkbox with text "I agree with these terms" next to it. It must be required for the "user" (also referred to as the "visitor") to check that box, otherwise the register form won't be submitted.

Checking out the form

To troubleshoot any problems we may encounter while editing the form, we 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.

Creating a fieldset

You want to create a fieldset for the Terms of Use, titled 'Terms of Use'.

<?php
function garland_preprocess_user_register(&$vars) {
 
// Adding the fieldset
 
$vars['form']['terms_of_use'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Terms of Use'),
   
'#weight' => 10,
  );
 
// passing the form to the template
 
$vars['form_markup'] = drupal_render($vars['form']);
 
// dsm($vars['form']);
}
?>

Without the '#weight' attribute, the fieldset rises to the top. You want it to sink to the bottom of the form.

Adding the content of a node to a fieldset

To place text or HTML in a form is not exactly adding a field to it. But still, markup that just sits there is a field in its own right and it is the default type for a field in the Drupal form API. Hence, we need not specify a '#type' of 'markup' for it.

<?php
function garland_preprocess_user_register(&$vars) {
 
// Adding the fieldset
 
$vars['form']['terms_of_use'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Terms of Use'),
   
'#weight' => 10,
  );
 
// Getting the node that contains the Terms of Use
 
$node = node_prepare(node_load(402));
 
// Adding the Terms of Use to the fieldset
 
$vars['form']['terms_of_use']['terms_of_use_text'] = array(
   
'#prefix' => '<div class="content clear-block">',
   
'#value' => $node->body,
   
'#suffix' => '</div>',
  );
 
// passing the form to the template
 
$vars['form_markup'] = drupal_render($vars['form']);
 
// dsm($vars['form']);
}
?>

We use node_prepare() to apply the filters that would normally be applied to the node's content if it were shown at Randy.com/terms-of-use. Likewise, using '#prefix' and '#suffix', we wrap the Terms of Use in a <div> with the same class names used for any node when shown on any other page.

Adding a checkbox to a fieldset

<?php
function garland_preprocess_user_register(&$vars) {
 
// Adding the fieldset
 
$vars['form']['terms_of_use'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Terms of Use'),
   
'#weight' => 10,
  );
 
// Getting the node that contains the Terms of Use
 
$node = node_load(402);
 
$node = node_prepare($node);
 
// Adding the Terms of Use to the fieldset
 
$vars['form']['terms_of_use']['terms_of_use_text'] = array(
   
'#prefix' => '<div class="content clear-block">',
   
'#value' => $node->body,
   
'#suffix' => '</div>',
  );
 
// Adding the Terms of Use to the fieldset
 
$vars['form']['terms_of_use']['I_agree'] = array(
   
'#type' => 'checkbox',
   
'#title' => t('I agree with these terms.'),
   
'#required' => TRUE,
  );
 
// passing the form to the template
 
$vars['form_markup'] = drupal_render($vars['form']);
 
// dsm($vars['form']);
}
?>

The checkbox will appear below the Terms following the order in which the form fields have been added to the fieldset.

The final register form.

Two unresolved issues

You ask yourself: Why in the hell did I create a template file when all it does is print a variable ? It doesn't even contain HTML markup! All it contains is this:

<?php print $form_markup; ?>

So, you've changed your mind about this. You delete the template file user-register.tpl.php. (You heard that using a template is 5x 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. The function becomes:

<?php
function garland_theme(){
  return array(
   
'user_register' => array(
     
'arguments' => array('form' => NULL),
     
// 'template' => 'user-register',
   
),
  );
}
?>

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_user_register ($form). In that new function, you replace all mention of $vars['form'] with $form. 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_user_register($form) {
 
// Adding the fieldset
 
$form['terms_of_use'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Terms of Use'),
   
'#weight' => 10,
  );
 
// Getting the node that contains the Terms of Use
 
$node = node_load(402);
 
$node = node_prepare($node);
 
// Adding the Terms of Use to the fieldset
 
$form['terms_of_use']['terms_of_use_text'] = array(
   
'#prefix' => '<div class="content clear-block">',
   
'#value' => $node->body,
   
'#suffix' => '</div>',
  );
 
// Adding the Terms of Use to the fieldset
 
$form['terms_of_use']['I_agree'] = array(
   
'#type' => 'checkbox',
   
'#title' => t('I agree with these terms.'),
   
'#required' => TRUE,
  );
 
// dsm($form);   
  // returning the themed form
 
return drupal_render($form); 
}
?>

Don't forget to clear the cache.

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

But I mentioned there were two problems. The second problem is that you cannot make that damn checkbox a requirement. It just doesn't work. I will leave this outstanding bug for you to fix. What's wrong here? Read the comments for the solution.

[]

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

Last edited by Caroline Schnapp about 14 years ago.

Comments

Nice tutorial! Both

Nice tutorial! Both interesting and funny, that's how i like it :)

Thanks

Thank you :-)

Thank you VERY much. :-)

why theming and not hook_form_alter?

Why is this done through theming?

Isn't it just easier to do it with a glue code module and implement hook_form_alter()?
Moreover, I bet the checkbox requirement will work then.
Altering forms in the theming layer is just too late for this, I guess, Drupal is already finished with its FAPI magic when the theming kicks in.

That's correct.

Altering forms in the theming layer is just too late for this, I guess, Drupal is already finished with its FAPI magic when the theming kicks in.

No, it should work. Actually I might have found a bug here. When I use the profile module to add required checkbox fields to this form, they're not marked required either. This worked in Drupal 5, and not in Drupal 6.

(Edit, but I think this could be a different bug. And that you may be correct that one cannot ADD fields from the theme layer.)

This is a theming exercise, this is the main reason I am not using hook_form_alter. The amount of work is pretty much the same, though, whether you add the Terms of Use using the theme layer, or go through the other door and create a module for the same purpose.

If you choose to use hook_form_alter you have to create a module, and enable it. I agree: that is easy. There is no hook_menu to implement in it, nothing special to do.
(You are, at the very least, a module developer wizard.)

By the way, you must know that the hook_form_alter function signature changed from Drupal 5 to Drupal 6. From this (D5):

<?php
function myModuleName_form_alter($form_id, &$form) {
  if (
$form_id == 'user_register') {
   
// do my thing to $form here...
 
}
  return
$form;
}
?>

To that (D6):

<?php
function myModuleName_form_alter(&$form, $form_state, $form_id) {
  if (
$form_id == 'user_register') {
   
// do my thing to $form here...
 
}
  return
$form;
}
?>

One note: a theming function will give you total control over the HTML output, whereas the use of hook_form_alter will give you complete control over the form's content. Sure enough, there is '#prefix' and '#suffix' and '#weight' you can play with, but there are puzzles hard to solve with a module alone. The use of drupal_render() is for themes only. It is only through the theme layer, for example, that I'm able to make a button added to a node form appear at the bottom of the form with the other buttons, for people with 'admin' permissions — hook_form_alter will not cut it. And that's only one example.

I have to do this:

<?php
/*
* Theming the node form
*/
function phptemplate_node_form($form) {
 
/* The buttons must be rendered first, because
   * they need to go to the bottom of the form.
   */
 
$buttons = drupal_render($form['buttons']);
  return
drupal_render($form) . $buttons;
}
?>

You can't do that with hook_form_alter() ...

I reproduced the problem in 2 ways

Moreover, I bet the checkbox requirement will work then.
Altering forms in the theming layer is just too late for this, I guess, Drupal is already finished with its FAPI magic when the theming kicks in.

I reproduced the problem in 2 ways.

First, I created a module that adds a required checkbox in the user_register form.

<?php
/*
* Implementation of hook_form_alter
*/
function kit_kat_form_alter(&$form, $form_state, $form_id) {
  if (
$form_id == 'user_register') {
   
// Adding a required checkbox
   
$form['I_want_a_kit_kat'] = array(
     
'#type' => 'checkbox',
     
'#title' => t('I want to eat a Kit Kat.'),
     
'#required' => TRUE,
    );
  }
  return
$form;
}
?>

The checkbox shows up, but it's not marked as required. And it sure isn't required to check it to submit the form.

Then I used the Profile module to create a 'checkbox' field. I used these options:

"The user must enter a value."
"Visible in user registration form."

[The profile field edit page.]

Same problem here.

An issue about this was posted on drupal.org 6 days ago. No one has replied to it yet. Well. I just did. (Oh man, I should remove the 'critical' thingy...)
http://drupal.org/node/259292

Bug in form.inc

I edited form.inc and I am now able to add truly required checkbox fields using the Profile module. Or, add them using hook_form_alter. But yes indeed I am unable to add a required checkbox from the theme layer, even after that fix to form.inc.

Soxofaan, you may be right. I may be crazy.

I need to use '#element_validate'

I added to Drupal CVS a simple module that circumvents the bug in form.inc. Using HOOK_form_alter(), I am able to do the same as I am doing here, ie: add Terms of Use with node_prepare() and node_load(), and add a required checkbox. The checkbox does not bypass validation this time because I do my own validation for it using '#element_validate'.

Unfortunately for the proper resolution of this 'tutorial', the '#element_validate' FAPI property is NOT taken into account within the theme layer. It is only executed when using HOOK_form_alter().

As it turns out...

1. Theming a form is NEVER the way to go to add a form element that requires validation. Using HOOK_form_alter — hence creating a module — is the way to go for adding elements to forms when these added elements require validation.

2. To circumvent the bug in form.inc, one has to handle his/her own validation for the required checkox, using '#element_validate'.

<?php
$form
['terms_of_use']['I_agree'] = array(
 
'#type' => 'checkbox',
 
'#title' => t('I agree with these terms.'),
 
'#required' => TRUE,
 
'#element_validate' => array('_terms_of_use_validate_checkbox'),
  ); 
?>

Where the function _terms_of_use_validate_checkbox(), defined in a module with name 'terms_of_use', is defined like so:

<?php
function _terms_of_use_validate_checkbox($form, &$form_state) {
 
$value = $form_state['values']['I_agree'];
  if (
$value == 0) {
   
form_set_error('I_agree', t('You must agree with the Terms of Use to get an account.'));
  }
}
?>

This works. The value of the checkbox will be 1 when the person who wants to sign up will have checked it.

I will rewrite this tutorial... and will create another one, more appropriate, to show how to theme forms that have no registered theming function. This wasn't a good example :-(

Robert Castelo just added a 'fix' module to CVS

He brings about an elegant fix to the problem.

(A release will soon appear on the project page at http://drupal.org/project/checkbox_validate. You can look at the code at http://cvs.drupal.org/viewvc.py/drupal/contributions/modules/checkbox_validate/)

<?php
// $Id:$

function checkbox_validate_form_alter(&$form, $form_state, $form_id) {
 
$form = checkbox_validate_recurse($form);
  return;
}

function
checkbox_validate_recurse($form_item) {
  if (!
is_array($form_item)) return;
  while (list(
$key, $value) = each($form_item)) {
    if (
strpos($key, '#') === 0) {
      if (
$form_item['#type'] == 'checkbox' && $form_item['#required'] == TRUE) {
       
$form_item['#element_validate'] = array('checkbox_validate_validation');
       
$form_item['#pre_render'][] = 'checkbox_validate_add_required_symbol';
        return
$form_item;
      }     
    }
    else {
     
$form_item[$key] = checkbox_validate_recurse($value, $key);
    }   
  }
  return
$form_item;
}    

// add required symbol here in this pre_render function
// so it doresn't display on error messages
function checkbox_validate_add_required_symbol($form) {
 
$form['#title'] .= ' <span class="form-required" title="' . t('This field is required.') . '">*</span>';
  return
$form;
}

function
checkbox_validate_validation($element) {
  if (empty(
$element['#value'])) form_error($element, t('!title field is required.', array('!title' => filter_xss_admin(($element['#title'])))));
  return;
}

function
checkbox_validate_theme() {
  return array(
   
'checkbox_validate_required' => array(
     
'arguments' => array(),
    ),
  );
}
?>

Hi, Caroline, I have a very

Hi, Caroline, I have a very big question this far and have asked drupal forum to no luck. Just in case you do know the answer:

Is it possible to merge registration and login, block or .tpl, into a single one .tpl? If it is, I'll be looking forward to your tutorial.

Thank you very much

Merge, you say?

What do you want to achieve exactly? A page or block that has to tabs?

The log in form cannot be merged with the register form as they do different things and have different fields...

Not sure I understand your question. Maybe you can provide some wireframe...?

It's like facebook. Login

It's like facebook. Login and register, all available in one page. The tabs must be removed, of course. The login form can be a block resides along with registration form. Or a registration form as a block embedded inside user-login.tpl or otherwise.

Yes, is that possible? I have tried placing login block inside user-register.tpl and also tried formblock.module to embed the registration block into user-login.tpl which is a variant of your tutorial above to no luck this far as when I hit the login button, the registration form always gives warning like email address required, and all required registration fields. There seems they both use a single button:) Thanks

Here is how I would do it

The login form can be a block [that] resides [...] with a registration form [appearing on the same page].

If you're interested in me doing it for you, I could give you a quote for that.

Here is how I would do it. I would theme the form with Form ID user-login-form, to remove all 'register' information. That will give you your User Login Block as per Facebook.

Then I would create a page node and using the PHP input format I would 'paste' the register form (form with Form ID user-register) on that page.

<?php
return drupal_get_form('user_register');
?>

So it is not a matter of creating a new template. It is a matter of creating a special page (or block) to display the register form, and a matter of theming the form with Form ID user-login-form.

And yet another hint: To theme the login form, you 'll have to register a theming function for it, as none has been registered...

thank you

for this article.

Thanks caroline for quick

Thanks caroline for quick reply. Gotta try it soon now. Will be back to report the progress. Thanks as always

You are welcome

:-)

Please do report back.

Yep, almost perfect. Easier

Yep, almost perfect. Easier than I thought. Here is what I did:
1. Remove all former functions related to registration and login stuffs.
2. Clear theme_registry, empty cache too
3. Started creating page node with the above code only with the url to go to, say /register
4. Create a block-user-0.tpl by copying from default block.tpl. All theming thing is done already through CSS only this far.
5. Since I print the login block from page.tpl, it soon appears side by side with the registration form
6. When I log in, it works. No more conflicts with the registration button. The funny thing is after I am successfully logged in, I am still facing/ stucked in the registration form page. I guess I have to do the redirection, right? Any suggestion? Can it be done through action .module and trigger.module? Please suggest me. I don't want logintoboggan, goto, and extra module yet.

Oh, and I also got this warning when later on trying to do the registration:

The selected file could not be copied, because no file by that name exists. Please check that you supplied the correct filename.

along with the successful message:

Your password and further instructions have been sent to your e-mail address.

Yes it almost answers my very big question in the simpler way than I thought. All respects to you, caroline.
Thanks

Using a block template

4. Create a block-user-0.tpl by copying from default block.tpl.php. All theming thing is done already through CSS only this far.

Actually this is much better. The form needs no tweaking, hence no theming. It is the link to create an account that must be removed, or hidden with CSS. Your approach is fantastic.

The funny thing is after I am successfully logged in, I am still facing/ stucked in the registration form page.

Of course. Then you really ought to use (as you intuited) a trigger and an action to redirect your user to the homepage.

Logintoboggan would be so easy though :-) Just configuring one parameter. But, yes, that's yet one more contributed module.

Hi, again. Been trying

Hi, again.
Been trying actions, token action and trigger to no luck. But when I log in as non-admin it finally redirects me to the correct URL. It seems that admin is never redirected. I wish I knew this admin thing, I wouldn't waste my precious 3 hours life. Hope this will never happen again to newbies like me. Phew!

I have also tried this as alternative to actions:

<?php $user; ?>
<?php if($user->uid):?>
<?php drupal_goto('user/'. $user->uid, NULL, NULL, 301);?>
<?php else: ?>
<?php return drupal_get_form('user_register'); ?>
<?php endif;?>

Thanks, Caroline for all the help. Now theming time:)

It i kept the folder where u siad but Devel not worknig.

* warning: array_map() [function.array-map]: Argument #2 should be an array in K:\wamp\www\drupal-6.4\drupal-6.4\modules\system\system.module on line 975.
* warning: array_keys() [function.array-keys]: The first argument should be an array in K:\wamp\www\drupal-6.4\drupal-6.4\includes\theme.inc on line 1720.
* warning: Invalid argument supplied for foreach() in K:\wamp\www\drupal-6.4\drupal-6.4\includes\theme.inc on line 1720.

Hmmm indeed

It i kept the folder where u siad but Devel not worknig.

Euhhh... what?

#disabled attribute not working?

I set #disabled attribute to textfield type (input), using this code:
<?php $form['title']['#disabled'] = true; ?>
But input is not disabled. When I check attribute via devel themer info, attribute is set correctly. Any idea what's wrong?

Cannot be done

This is not your fault, and not a bug.

By the time a form reaches the 'theme layer', it is too late to set a form element to 'required' or 'disabled'. You would need to use HOOK_form_alter in a module to disable the textfield.

However, you can use jQuery to disable the field. Hence you can still use theming to disable the field. Use drupal_add_js... from your theme.

Try the following jQuery:

jQuery('input[name=title]').attr('disabled', 'disabled');

How to theme username field?

Hi,
how can you change the textfield size of the username and email id fields (in create new account). The defaults value of these are 60. I would like to reduce the size of these textfields

You can apply a CSS width

Apply a CSS width to these text fields.

Thanks. Where can I find...

Thanks. Where can I find the CSS rule for the same? I checked in the module "user", but couldnt really get the CSS rule for the username filed. Could you please let me know where exactly to make the change.

In your theme's stylesheet

You add a CSS rule to your theme's main stylesheet. You make sure the rule is specific enough to be applied by the browser (specific as in specificity).

I don't know what theme you're using, but please don't tell :) Using Firebug, you will be able to determine how to write your CSS rule. It's beyond (or should I say below) the scope of my tutorial.

This article is a chapter in a great book, The Art and Science of CSS, and is a good introduction to form theming with CSS.

For prototyping forms, or for juggling with wild form ideas, you can use Adobe Fireworks, or sign up for a free account over at wufoo.

Really a great addition. I

Really a great addition. I have read this marvelous post. Thanks for sharing information about it. I really like that. Thanks so lot for your convene.
Hot face

Excellent Tutorial

Thanks for this clear step by step tutorial. I had been struggling on how to override the fieldset title that is automatically assigned when a taxonomy vocabulary is required for a particular content type on a cck node form, but with your excellent instructions on Drupal 6 form theming I figured it out. Thanks again.

You're welcome.

:)

Theming the cck node form in Drupal 6

Hi this is a great guide for user register form.
does there exist any guide for theming the cck node form in Drupal 6?
I am lokking for it days and nights but no success.

Thx in advance.
HoKe

how to store the new user_register field values

First let me thaaaank you sooo much for this amazing and such a funny tutorial, it's just a light in the darkness :)

All the steps above are perfect, now i have a site with custom comment and register forms.

and here is the result

by the way, i have 3 new text fields in the user_register form, but when i fill and submit it, these fields are not saved in my database.

when i take a look of the new users profiles seems like there's nothing new :(

my function:

function X_theme_preprocess_user_register(&$vars) {
	// Campo de texto para Nombre
  $vars['form']['nombre'] = array(
    '#type' => 'textfield',
    '#title' => t('Nombre'),
  	'#required' => TRUE,
    '#weight' => 10,
  );
  	// Campo de texto para Direccion
  $vars['form']['direccion'] = array(
    '#type' => 'textfield',
    '#title' => t('Direccion'),
  	'#required' => TRUE,
    '#weight' => 10,
  );
  	// Campo de texto para Telefono
  $vars['form']['telefono'] = array(
    '#type' => 'textfield',
    '#title' => t('Telefono'),
  	'#required' => TRUE,
    '#weight' => 10,
  );
 
 
  // La variable que recibe el template
  $vars['form_markup'] = drupal_render($vars['form']);

any idea would save my life ñ_ñ

S.O.S! Caroline please :P

Unfortunately you can't do this

Edwin, at the theme layer, you can't add form fields and expect their content to be saved to the database. You have to use a module for this. Look up the module hook hook_form_alter.

Populating Drop Down Lists

Is it possible to populate list boxes from checkboxes?

I have a series of listboxes which the user checks and then uses a drop down list to pick his primary selection. There is also an option called 'Other' which I also want to add to the dropdown list.

Is this possible?

Thanks for a very rewarding and entertaining article...

Hello Caroline! Thanks for

Hello Caroline!

Thanks for wonderful tutorial of yours very good constucted and helpful.
But one thing drives me crazy.
How to move existing fields (that i don't make) to my created fieldset ?

for example i created fieldset
$vars['form']['reg_info']
and i know standart drupal 6 registration form has
$vars['form']['name']
so how would i make this to become
$vars['form']['reg_info']['name']
So it can be in that fieldset.

All i want is just visually move field to fieldset.
Ofc if i would create my own field that wouldn't be a problem, but this is standart drupal field.

Thanks.

PHP code in .tpl.php file executes, but not wt HTML code present

Thanks for the great tutorial; it simplifies a great deal of thinking. I have not, however, reproduced the example entirely. My problem occurs when I execute the following code in user-register.tpl.php:

<h2>Hello world.</h2><?php print $form_markup; ?>

All that shows at ../user/register is 'Hello world.' When I remove the elements and (importantly for some reason) eliminate whitespace at the top of the file to leave just the php code the register form is back.

But that's not the point of the exercise, so I'm wondering if you can help point out where I'm going wrong.

Thanks and happy holidays!

Thanks

There are two things that separate this article from others

  • Not only does you learn how to do the task, you also learn other interesting tibits of information that will aid you in your Drupal journey
  • The lesson is entertaining, funny and informative

Thanks for everything.

If drupal doc was like that

Excellent tutorial, I wish Drupal documentation was as clear as you...

doing this is module form with specific form alter

Apologize if this is already posted here, but I refer to it this way these days...

function small_tweaks_form_user_register_alter(&$form, &$form_state) {
	// mess with the $form
}

http://api.drupal.org/api/function/hook_form_FORM_ID_alter/6

BONUS, here's what I use to cluster all profile fields into one fieldset.

function MYMODULE_form_user_register_alter(&$form, &$form_state) {
	$profileFieldset= 'About You'; // CHANGE THIS
	// cluster all fields into one fieldset
	foreach($form[$profileFieldset] as $key => $item) {
		// push field to account fieldset
		if( substr($key,0,8)=='profile_' ) $form['account'][$key] = $item;
	}
	unset($form[$profileFieldset]);
}

(assumes you use the convention of "profile_x" for your field names