In the following sections, we’ll be using this figure as a guide and describing what happens along the way.
Figure 11-1. How Drupal handles forms
In order to interact with the forms API intelligently, it’s helpful to know how the engine behind the API works. Modules describe forfor the forms to Drupal using associative arrays. Drupal’s form engine takes care of generating HTML for the forms to be
displayed and securely processing submitted forms using three phases: validation, submission, and redirection. The following sections explain what happens when you call drupal_get_form().
Initializing the Process
There are three variables that are very important when dealing with forms. The first, $form_id, contains a string identifying the form.The second, $form, is a structured array describing the form. And the third, $form_state, contains information about the form,
such as the form’s values and what should happen when form processing is finished. drupal_get_form() begins by initializing
$form_state.
Setting a Token
One of the form system’s advantages is that it strives to guarantee that the form being submitted is actually the form that Drupal created, for security and to counteract spammers or would-be site attackers. To do this, Drupal sets a private key for each Drupal
installation. The key is generated randomly during the installation process and distinguishes this particular Drupal installation from other installations of Drupal. Once the key is generated, it’s stored in the variables table as drupal_private_key. A pseudo-random token
based on the private key is sent out in the form in a hidden field and tested when the form is submitted. See http://drupal.org/
node/28420 for background information. Tokens are used for logged-in users only, as pages for anonymous users are usually cached,
resulting in a non-unique token.
Setting an ID
A hidden field containing the form ID of the current form is sent to the browser as part of the form. This ID usually corresponds with the function that defines the form and is sent as the first parameter of drupal_get_form(). For example, the function user_
register() defines the user registration form and is called this way:
$output = drupal_get_form('user_register');
Collecting All Possible Form Element Definitions
Next, _element_info() is called. This invokes hook_element_info() on all modules that implement it. Within Drupal core, the
standard elements, such as radio buttons and check boxes, are defined by modules/system/system.module’s implementation of hook_element_info(). Modules implement this hook if they want to define their own element types. You might implement hook_element_info() in your module because you want a special kind of form element, like an image upload button that shows you athumbnail
during node preview, or because you want to extend an existing form element by defining more properties. For example, the
contributed five star module defines its own element type:
/**
* Implements hook_elements().
*
* Defines 'fivestar' form element type.
*/
function fivestar_element_info() {
$type['fivestar'] = array( '#input' => TRUE, '#stars' => 5, '#widget' => 'stars', '#allow_clear' => FALSE, '#auto_submit' =>FALSE, '#auto_submit_path' => '', '#labels_enable' => TRUE, '#process' => array('fivestar_expand'),);
return $type;}
And the TinyMCE module uses hook_element_info() to potentially modify the default properties of an existing type. TinyMCE
adds a #process property to the textarea element type so that when the form is being built, it will calltinymce_process_textarea(),
which may modify the element. The #process property is an array of function names to call.
/**
* Implements hook_elements().
*/
function tinymce_element_info() {
$type = array();
if (user_access('access tinymce')) {
// Let TinyMCE potentially process each textarea.
$type['textarea'] = array('#process' => array('tinymce_process_textarea'),);}
return $type;}
Looking for a Validation Function
A validation function for a form can be assigned by setting the #validate property in the form to an array with the function name
as the value. Multiple validators may be defined in this way:
// We want foo_validate() and bar_validate() to be called during form validation.
$form['#validate'][] = 'foo_validate';
$form['#validate'][] = 'bar_validate';
// Optionally stash a value in the form that the validator will need
// by creating a unique key in the form.
$form['#value_for_foo_validate'] = 'baz';
If there is no property named #validate in the form, the next step is to look for a function with the name of the form ID plus _validate. So if the form ID is user_register, the form’s #validate property will be set to user_register_validate.
Looking for a Submit Function
The function that handles form submission can be assigned by setting the #submit property in the form to an array with the name of the function that will handle form submission:
// Call my_special_submit_function() on form submission.
$form['#submit'][] = 'my_special_submit_function';
// Also call my_second_submit_function().
$form['#submit'][] = 'my_second_submit_function';
If there is no property named #submit, Drupal tests to see if a function named with the form ID plus_submit exists. So if the form
ID is user_register, Drupal sets the #submit property to the form processor function it found—that is, user_register_submit.
Allowing Modules to Alter the Form Before It’s Built
Before building the form, modules have two chances to alter the form. Modules can implement a function named from the
form_id plus_alter, or they may simply implement hook_form_alter(). Any module that implements either of these can modify
anything in the form. This is the primary way to change, override, and munge forms that are created by modules other than your
own.
Building the Form
The form is now passed to form_builder(), which processes through the form tree recursively and adds standard required values. This function also checks the #access key for each element and denies access to form elements and their children if #access is
FALSE for the element.
Allowing Functions to Alter the Form After It’s Built
Each time form_builder() encounters a new branch in the $form tree (for example, a new fieldset or form element), it looks for a property called #after_build. This is an optional array of functions to be called once the current form element has been built. When the entire form has been built, a final call is made to the optional functions whose names may be defined in $form['#after_build']. All #after_build functions receive $form and $form_state as parameters. An example of its use in core is during the display of the
file system path at Configuration -> File system. An #after_build function (in this case system_check_directory()) runs to determine
if the directory does not exist or is not writable and sets an error against the form element if problems are encountered.
Checking If the Form Has Been Submitted
If you’ve been following along in Figure 11-1, you’ll see that we have come to a branch point. If the form is being displayed for
the first time, Drupal will go on to create the HTML for the form. If the form is being submitted, Drupal will go on to process thedata that was entered in the form; we’ll come back to that case in a moment (see the “Validating the Form” section later in the
chapter). We’ll assume for now the form is being displayed for the first time. It is important to realize that Drupal does all of the
work described previously both when a form is being displayed for the first time and when a form is being submitted.
Finding a Theme Function for the Form
If $form['#theme'] has been set to an existing function, Drupal simply uses that function to theme the form. If not, the theme
registry is checked for an entry that corresponds with the form ID of this form. If such an entry is found, the form ID is assigned to$form['#theme'], so later when Drupal renders the form, it will look for a theme function based on the form ID. For example, if theform ID is taxonomy_overview_terms, Drupal will call the corresponding theme function theme_taxonomy_overview_terms().
Of course, that theme function could be overridden by a theme function or template file in a custom theme; see Chapter 8 for details on how them able items are themed.
Allowing Modules to Modify the Form Before It’s Rendered
The only thing left to do is to transform the form from a data structure to HTML. But just before that happens, modules have a
last chance to tweak things. This can be useful for multipage form wizards or other approaches that need to modify the form at the
last minute. Any function defined in the $form['#pre_render'] property is called and passed the form being rendered.
Rendering the Form
To convert the form tree from a nested array to HTML code, the form builder calls drupal_render(). This recursive function goes
through each level of the form tree, and with each, it performs the following actions:
1. Determine if the #children element has been defined (synonymous with content having been generated for this element); if not, render the children of this tree node as follows:
• Determine if a #theme function has been defined for this element.
• If so, temporarily set the #type of this element to markup. Next, pass this element to the #theme function, and reset the
element back to what it was.
• If no content was generated (either because no #theme function was defined for this element or because the call to the #theme
function was not found in the theme registry or returned nothing), each of the children of this element is rendered in turn
(i.e., by passing the child element to drupal_render()).
• On the other hand, if content was generated by the #theme function, store the content in the #children property of this element
2. If the element itself has not yet been rendered, call the default theme function for the #type of this element. For example, if
this element is a text field in a form (i.e., the #type property has been set to textfield in the formdefinition), the default theme
function will be theme_textfield(). If the #type of this element has not been set, default to markup. Default theme functions for
core elements such as text fields are found in includes/form.inc.
3. If content was generated for this element and one or more function names are found in the #post_render property, call each
of them, and pass the content and the element. The #post_render function(s) must return the final content.
4. Prepend #prefix and append #suffix to the content, and return it from the function.
The effect of this recursive iteration is that HTML is generated for every level of the form tree. For example, in a form with a
field set with two fields, the #children element of the field set will contain HTML for the fields inside it, and the #children
element of the form will contain all of the HTML for the form (including the fieldset’s HTML).
This generated array, ready to be rendered, is then returned to the caller of drupal_get_form(). That’s all it takes! We’ve reached the “Return HTML” endpoint in Figure 11-1.
Validating the Form
Now let’s go back in Figure 11-1, to the place where we branched off in the section “Checking If the Form Has Been
Submitted.” Let’s assume that the form has been submitted and contains some data; we’ll take the other branch and look at that
case. Drupal’sform processing engine determines whether a form has been submitted based on $_POST being nonempty and the
presence of a string value in $_POST['form_id'] that matches the ID of the form definition that was just built (see the “Setting an ID” section). When a match is found, Drupal validates the form.
The purpose of validation is to check that the values that are being submitted are reasonable. Validation will either pass or fail. If
validation fails at any point, the form will be redisplayed with the validation errors shown to the user. If all validation passes, Drupal will move on to the actual processing of the submitted values.
Token Validation
The first check in validation is to determine whether this form uses Drupal’s token mechanism (see the “Setting a Token” section).
All Drupal forms that use tokens have a unique token that is sent out with the form and expected to be submitted along with other
form values. If the token in the submitted data does not match the token that was set when the form was built, or if the token is absent, validation fails (though the rest of validation is still carried out so that other validation errors can also be flagged).
Built-In Validation
Next, required fields are checked to see if the user left them empty. Fields with a #maxlength property are checked to make sure
the maximum number of characters has not been exceeded. Elements with options (check boxes, radio buttons, and drop-down selection fields) are examined to see if the selected value is actually in the original list of options present when the form was built.
Element-Specific Validation
If there is an #element_validate property defined for an individual form element, the functions defined in the property are called andpassed the $form_state and $element.
Validation Callbacks
Finally, the form ID and form values are handed over to the validation function(s) specified for the form (usually the name of the form ID plus _validate).
Submitting the Form
If validation passes, it’s time to pass the form and its values to a function that will finally do something as a result of the form’s
submission. Actually, more than one function could process the form, since the #submit property can contain an array of function
names. Each function is called and passed $form and $form_state.
Redirecting the User
The function that processes the form should set $form_state['redirect'] to a Drupal path to which the user will be redirected, such
as node/1234. If there are multiple functions in the #submit property, the last function to set $form_state['redirect'] will win. If
no function sets $form_state['redirect'] to a Drupal path, the user is returned to the same page (that is, the value of $_GET['q']).
The redirect set in $form_state['redirect'] by a submit function can be overridden by defining a value such as
$form_state['redirect'] = 'node/1' or $form_state['redirect'] = array('node/1', $query_string, $named_anchor)
Using the parameter terms used in drupal_goto(), the last example could be rewritten as follows:
$form_state['redirect'] = array('node/1', $query, 302)
Determination of form redirection is carried out by drupal_redirect_form() in includes/form.inc. The actual redirection is carried
out by drupal_goto(), which returns a Location header to the web server. The parameters that drupal_goto() takescorrespond to
the members of the array in the latter example: drupal_goto($path = '', $options = array(), $http_response_code = 302)
Creating Basic Forms
If you come from a background where you have created your own forms directly in HTML, you may find Drupal’s approach a bit baffling at first. The examples in this section are intended to get you started quickly with your own forms. To begin, we’llwrite a simple module that asks you for your name and prints it on the screen. We’ll put it in our own module, so we don’t have to modify any existing code. Our form will have only two elements: the text input field and a Submit button. We’ll start by creating a .info file
at sites/all/modules/custom/form example/form example.info and entering the following:
name = Form Example
description = Shows how to build a Drupal form package = Pro Drupal Development
core = 7.x files[]=formexample.module
Next, we’ll put the actual module into sites/all/modules/custom/formexample/formexample.module:
<?php
/**
* @file
* Play with the Form API.
*/
/**
* Implements hook_menu().
*/
function formexample_menu() {
$items['formexample'] = array( 'title' => 'View the sample form','page callback' => 'drupal_get_form',
'page arguments' => array('formexample_nameform'), 'access callback' => TRUE, 'type' => MENU_NORMAL_ITEM);
return $items;}
/**
* Define a form.
*/
function formexample_nameform() {
$form['user_name'] = array( '#title' => t('Your Name'), '#type' => 'textfield',
'#description' => t('Please enter your name.'),);
$form['submit'] = array( '#type' => 'submit', '#value' => t('Submit'));
return $form;
}
/**
* Validate the form.
*/
function formexample_nameform_validate($form, &$form_state) { if ($form_state['values'
['user_name'] == 'King Kong') {
// We notify the form API that this field has failed validation.
form_set_error('user_name',t('King Kong is not allowed to use this form.'));}}
/**
* Handle post-validation form submission.
*/
function formexample_nameform_submit($form, &$form_state) {
$name = $form_state['values']['user_name']; drupal_set_message(t('Thanks for filling out the form, %name',
array('%name' => $name)));}
We’ve implemented the basic functions you need to handle forms: one function to define the form, one to validate it, and one to
handle form submission. Additionally, we implemented a menu hook so that visitors can get to our form. Our simple form should
look like the one shown in Figure 11-2.
Figure 11-2. A basic form for text input with a Submit button
The bulk of the work goes into populating the form’s data structure, that is, describing the form to Drupal. This information is
contained in a nested array that describes the elements and properties of the form and is typically contained in a variable called
$form. The important task of defining a form happens in form example_name form() in the preceding example, where we’re
providing the minimum amount of information needed for Drupal to display the form.
Form Properties
Some properties can be used anywhere, and some can be used only in a given context, like within a button. For a complete list of
properties, see the end of this chapter. Here’s a more complex version of a form than that given in our previous example:
$form['#method'] = 'post';
$form['#action'] = 'http://example.com/?q=foo/bar';
$form['#attributes'] = array( 'enctype' => 'multipart/form-data','target' => 'name_of_target_frame');
$form['#prefix'] = '<div class="my-form-class">';
$form['#suffix'] = '</div>';
The #method property defaults to post and can be omitted. The get method is not supported by the form API and is not usually used in Drupal, because it’s easy to use the automatic parsing of arguments from the path by the menu routing mechanism.The
#action property is defined in system_element_info() and defaults to the result of the function request_uri(). This is typically the
same URL that displayed the form.
Form IDs
Drupal needs to have some way of uniquely identifying forms, so it can determine which form is submitted when there are multiple
forms on a page and can associate forms with the functions that should process that particular form. To uniquely identify a form,
we assign each form a form ID. The ID is defined in the call to drupal_get_form(), like this:
drupal_get_form('mymodulename_identifier');
For most forms, the ID is created by the convention “module name” plus an identifier describing what the form does. For example,
the user login form is created by the user module and has the ID user_login. Drupal uses the form ID to determine the names ofthe default validation, submission, and theme functions for the form. Additionally, Drupal uses the form ID as a basis for generatingan HTML ID attribute in the <form> tag for that specific form, soforms in Drupal always have a unique ID. You
can override the ID by setting the #id property:
$form['#id'] = 'my-special-css-identifier';
The resulting HTML tag will look something like this:
<form action="/path" "accept-charset="UTF-8" method="post" id="my-special-css-identifier">
The form ID is also embedded into the form as a hidden field named form_id. In our example, we chose form example_name form as the form ID because it describes our form. That is, the purpose of our form is for the user to enter his or her name.
We could have just used formexample_form, but that’s not very descriptive—and later we might want to add another form to our module.
Fieldsets
Often, you want to split your form up into different fieldsets—the form API makes this easy. Each field set is defined in the data structure and has fields defined as children. Let’s add a favorite color field to our example:
function formexample_nameform() {
$form['name'] = array( '#title' => t('Your Name'), '#type' => 'fieldset','#description' => t('What people call you.'));
$form['name']['user_name'] = array( '#title' => t('Your Name'), '#type' => 'textfield',
'#description' => t('Please enter your name.'));
$form['color'] = array( '#title' => t('Color'), '#type' => 'fieldset', '#description' => t('This fieldset contains the Color field.'),
'#collapsible' => TRUE, '#collapsed' => FALSE);
$form['color_options'] = array( '#type' => 'value','#value' => array(t('red'), t('green'), t('blue')));
$form['color']['favorite_color'] = array( '#title' => t('Favorite Color'),'#type' => 'select', '#description' =>
t('Please select your favorite color.'),'#options' => $form['color_options']['#value']);
$form['submit'] = array( '#type' => 'submit', '#value' => t('Submit'));
return $form;}
The resulting form looks like the one shown in Figure 11-3.
Figure 11-3. A simple form with field sets
We used the optional #collapsible and #collapsed properties to tell Drupal to make the second fieldset collapsible using JavaScript
by clicking the fieldset title. Here’s a question for thought: when $form_state['values'] gets passed to the validate and submit
functions, will the color field be $form_state['values']['color']['favorite_color'] or $form_state['values']['favorite_color']?
In other words, will the value be nested inside the fieldset or not? The answer: it depends. By default, the form processor flattens theform values, so that the following function will work correctly:
function formexample_nameform_submit($form_id, $form_state) {
$name = $form_state['values']['user_name'];
$color_key = $form_state['values']['favorite_color'];
$color = $form_state['values']['color_options'][$color_key];
drupal_set_message(t('%name loves the color %color!', array('%name' => $name, '%color' => $color)));}
The message set by the updated submit handler can be seen in Figure 11-4.
Figure 11-4.Message from the submit handler for the form
If, however, the #tree property is set to TRUE, the data structure of the form will be reflected in the names of the form values. So, if in our form declaration we had said $form['#tree'] = TRUE; then we would access the data in the following way:
function formexample_nameform_submit($form, $form_state) {
$name = $form_state['values']['name']['user_name'];
$color_key = $form_state['values']['color']['favorite_color'];
$color = $form_state['values']['color_options' [$color_key]; drupal_set_message(t('%name loves the color %color!', array('%name' => $name, '%color' => $color)));
Theming Forms
Drupal has built-in functions to take the form data structure that you define and transform, or render, it into HTML.
However, often you may need to change the output that Drupal generates, or you may need fine-grained control over the process.
Fortunately, Drupal makes this easy.
Using #prefix, #suffix, and #markup
If your theming needs are very simple, you can get by with using the #prefix and #suffix attributes to add HTML before and/or
after form elements:
$form['color'] = array( '#prefix' => '<hr />', '#title' => t('Color'), '#type' => 'fieldset', '#suffix' => '<div class="privacy-warning">' .t('This information will be displayed publicly!') . '</div>',);
This code would add a horizontal rule above the Color fieldset and a privacy message below it, as shown in Figure 11-5.
Figure 11-5. The #prefix and #suffix properties add content before and after an element.
You can even declare HTML markup as type #markup in your form (though this is not widely used). Any form element without a #type property defaults to markup.
$form['blinky'] = array('#markup' => '<blink>Hello!</blink>');
Using a Theme Function
The most flexible way to theme forms is to use a theme function specifically for that form or form element. There are two steps
involved. First, Drupal needs to be informed of which theme functions our module will be implementing. This is done through hook_
theme() (see Chapter 9 for details). Here’s a quick implementation of hook_theme() for our module, which basically says
“Our module provides two theme functions and they can be called with no extra arguments”:
/**
* Implements hook_theme().
*/
function formexample_theme() { return array(
'formexample_nameform' => array( 'render element' => 'form',
'template' => 'formexample-nameform',
),
);
}
formexample-nameform.tpl.php.
The next step is to use a template preprocess function to gather all of the elements from the form and make those elements available individually so that the themer can control how each element is displayed on the form. The following function assigns each form element to a variable with the key of the variable array being the name of the field—e.g., $variable['formexample_formname']['name'] is the variable containing the text box used to render that field on the form.
/**
* Assign the elements of the form to variables so
* the themer can use those values to control how the
* form elements are displayed, or alternatively
* displaying the whole form as constructed above.
*/
function template_preprocess_formexample_nameform(&$variables) {
$variables['formexample_nameform'] = array();
$hidden = array();
// Provide variables named after form keys so themers can print each element independently.
foreach (element_children($variables['form']) as $key) {
$type = $variables['form'][$key]['#type'];
if ($type == 'hidden' || $type == 'token') {
$hidden[] = drupal_render($variables['form'][$key]);
}
else {
$variables['formexample_nameform'][$key] = drupal_render($variables['form'][$key]);
}
}
// Hidden form elements have no value to themers. No need for separation.
$variables['formexample_nameform']['hidden'] = implode($hidden);
// Collect all form elements to make it easier to print the whole form.
$variables['formexample_nameform_form'] = implode($variables['formexample_nameform']);
}
The next step is to create the .tpl.php file that Drupal will use to render the form. In the sample code here, I am printing each of the form’s fields and have moved the color option above the name field by printing that form field first.
<?php
/**
* @file
*
* This is the template file for rendering the formexample nameform.
* In this file each element of the form is rendered individually
* instead of the entire form at once, giving me the ultimate control
* over how my forms are laid out. I could also print the whole form
* at once - using the predefined layout in the module by
* printing $variables['formexample_nameform_form'];
*
*/
print '<div id="formexample_nameform">';
print $variables['formexample_nameform']['color']; print $variables['formexample_nameform']['name'];
print $variables['formexample_nameform']['submit']; print $variables['formexample_nameform']['hidden']; print '</div>';
// print $formexample_nameform_form;
?>
Telling Drupal Which Theme Function to Use
You can direct Drupal to use a function that does not match the formula “theme_ plus form ID name” by specifying a #theme property for a form:
// Now our form will be themed by the function
// theme_formexample_alternate_nameform().
$form['#theme'] = 'formexample_alternate_nameform';
// Theme this fieldset element with theme_formexample_coloredfieldset().
$form['color'] = array( '#title' => t('Color'), '#type' => 'fieldset',
'#theme' => 'formexample_coloredfieldset'
);
Note that, in both cases, the function you are defining in the #theme property must be known by the theme registry; that is,it must be declared in a hook_theme() implementation somewhere.
Không có nhận xét nào:
Đăng nhận xét