Thứ Sáu, 30 tháng 5, 2014

Understanding Form Processin[The Form API]

Figure  11-1 shows an overview of the form building, validation, and  submission process.
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 sitattackers. 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 adrupal_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 test 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_alteror 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 alon 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 buil (see the “Setting an ID” section). When  a match is found, Drupal validates the form.

The purpose of validation is to chec 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 chec 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
nfunction sets $form_state['redirect'] to a Drupa 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 bibaffling  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 dont have to modify any existing code. Our form will have only two elements: the text input field and  a Submit button. We’ll start bcreating 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 Figur 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
$formThe 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_loginDrupal 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 belo 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 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',
),
);
}

The template attribute specifies that the template file used to render this form will be named
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 eac 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';

Or you can tell Drupal to use a special theme function for just one element of a form:

// 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