第10章 drupal表单(form)API

老葛的Drupal培训班 Think in Drupal

Drupal提供了一个应用程序接口(API),用来生成、验证和处理HTML表单。表单API将表单抽象为一个嵌套数组,里面包含了属性和值。在生成页面时,表单呈现引擎会在适当的时候将数组呈现出来。这种方式包含多层含义:
 
              我们没有直接输出HTML,而是创建了一个数组并让引擎生成HTML。
 
由于我们将表单的表示作为结构化的数据进行处理,所以我们可以添加、删除、重新排序、和修改表单。当你想用一种干净利索的方式对其它模块创建的表单进行修改时,这会特别方便。
 
任意的表单元素可以映射到任意的主题函数上。
 
可以将额外的表单验证或处理函数添加到任意表单上。
 
对表单操作进行了保护,从而防止表单注入攻击,比如当用户修改了表单并接着试图提交它时。
 
使用表单的学习曲线有点高
 
在本章中,我们迎难而上。我们将学习表单引擎的工作原理;如何创建、验证、处理表单;以及当我们需要个性化外观时如何编写主题函数:本章所讲的都是在Drupal6中所实现的表单API。我们首先检查一下表单处理引擎的工作原理。如果你是刚刚接触Drupal的表单,需要一个实际的例子作为起步,那么你可以直接跳到后面的“创建基本的表单”一节。如果你想查找单个表单属性的详细,那么你可以参看本章最后的“表单API属性”一节。
 

Drupal版本:

理解drupal表单处理流程

 

图10-1展示了表单构建、验证、和提交流程的概览。在下面的部分中,我们将使用该图作为指南,向大家描述各步骤的细节。
图10-1 Drupal是如何处理表单的
 
为了更好的与表单API进行交互,理解API背后引擎的工作原理,将会对你非常有用。模块使用关联数组向Drupal描述表单。Drupal的表单引擎负责为要显示的表单生成HTML,并使用三个阶段来安全的处理提交了的表单:验证、提交、重定向。接下来的部分解释了,当调用drupal_get_form()时都发生了什么。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

流程初始化

 

流程初始化
在处理表单时,有3个变量非常重要。第一个就是$form_id,它包含了一个标识表单的字符串。第二个就是$form,它是一个描述表单的结构化数组。而第三个就是$form_state,它包含了表单的相关信息,比如表单的值以及当表单处理完成时应该发生什么。drupal_get_form()在开始时,首先会初始化$form_state。
 
设置一个令牌(token)
       表单系统的一个优点是,它尽力的去保证被提交的表单就是Drupal实际创建的,这主要是为了安全性和防止垃圾信息或潜在的站点攻击者。为了实现这一点,Drupal为每个Drupal安装都设置了一个私钥。这个私钥是在安装流程期间随机生成的,它能将这个特定的Drupal安装与其它的Drupal区别开来。一旦私钥生成后,它将作为drupal_private_key存储在variables表中。Drupal将基于私钥生成一个随机的令牌,而该令牌将作为隐藏域发送到表单中。当表单提交时,会对令牌进行测试。相关背景信息请参看drupal.org/node/28420。令牌仅用于登录用户,因为匿名用户的页面通常会被缓存起来,这样它们就没有唯一的令牌了。
 
设置一个ID
一个包含了当前表单ID的隐藏域,将作为表单的一部分被发送给浏览器。该ID一般对应于定义表单的函数,它将作为drupal_get_form()的第一个参数传递过来。例如函数user_register()定义了用户注册表单,它的调用方式如下:
 
$output = drupal_get_form('user_register');

老葛的Drupal培训班 Think in Drupal

Drupal版本:

收集所有可能的表单元素定义

老葛的Drupal培训班 Think in Drupal

接着,调用element_info()。它将调用所有实现了hook_elements()的模块上的这个钩子函数。在Drupal核心中,标准的元素,比如单选按钮和复选框,都定义在modules/system/system.module中的hook_elements()实现中(参看system_elements())。如果模块需要定义它们自己的元素类型,那么就需要实现这个钩子。在以下几种情况中,你可能需要在你的模块中实现hook_elements():你想要一个特殊类型的表单元素时,比如一个图像上传按钮,在节点预览期间可用来显示缩略图;或者,你想通过定义更多的属性来扩展已有的表单元素时。
    例如,第3方的fivestar模块定义了它自己的元素类型:
 
/**
 * Implementation of hook_elements().
 *
 * Defines 'fivestar' form element type.
 */
function fivestar_elements() {
    $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;
}
    TinyMCE模块使用hook_elements(),来潜在地修改已有类型的默认属性。TinyMCE向textarea元素类型添加了一个#process属性,这样当表单正被构建时,它将调用tinymce_process_textarea(),这样就能够修改表单元素了。#process属性是一个数组,里面包含了所要调用的函数名字。
 
/**
 * Implementation of hook_elements().
 */
function tinymce_elements() {
    $type = array();
 
    if (user_access('access tinymce')) {
        // Let TinyMCE potentially process each textarea.
        $type['textarea'] = array(
            '#process' => array('tinymce_process_textarea'),
        );
    }
 
    return $type;
}
 
    钩子element_info()为所有的表单元素收集所有的默认属性,并将其保存到一个本地缓存中。在进入下一步----为表单寻找一个验证器----以前,对于那些在表单定义中尚未出现的任何默认属性,都将在这里被添加进来。

Drupal版本:

寻找一个验证函数

老葛的Drupal培训班 Think in Drupal

通过将表单的属性#validate设置为一个数组,其中函数名为键,一个数组作为值,从而为表单分配一个验证函数。在调用验证函数时,后面的数组中的任何数据都将被传递给验证函数。可以使用下面的方式来定义多个验证器:
 
// 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';
 
如果表单中没有定义属性#validate,那么接下来就要寻找名为“表单ID”+“_validate”的函数。所以,如果表单ID为user_register,那么表单的#validate属性将被设置为user_register_validate。

Drupal版本:

寻找一个提交函数

老葛的Drupal培训班 Think in Drupal

通过将表单的#submit属性设置为一个数组,其中以函数名为键,这里的函数名就是用来处理表单提交的函数的名字,从而为表单分配一个提交函数:
 
// 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';
 
    如果表单没有名为#submit的属性,那么接下来就要寻找名为“表单ID”+“_submit”的函数。所以,如果表单ID为user_register,那么Drupal将把#submit属性设置为它所找到的表单处理器函数;也就是user_register_submit。

Drupal版本:

允许drupal模块在表单构建以前修改表单

 

允许drupal模块在表单构建以前修改表单
    在构建表单以前,模块有两个可以修改表单的机会。模块可以实现一个名字源于form_id + _alter的函数,或者可以简单的实现hook_form_alter()。任何模块,只要实现了这两个钩子中的任意一个,那么就可以修改表单中的任何东西。对于由第3方模块创建的表单,我们主要可以使用这种方式对其进行修改、覆写、混合。
 
构建表单
    现在表单被传递给了form_builder(),这个函数将对表单树进行递归处理,并为其添加标准的必须值。这个函数还将检查每个元素的#access键,如果该元素的#access为FALSE,那么将拒绝对该表单元素及其子元素的访问。
 
允许函数在表单构建后修改表单

函数form_builder()每次遇到$form树中的一个新分支时(例如,一个新的字段集或表单元素),它都寻找一个名为#after_build的可选属性。这是一个可选的数组,里面包含了当前表单元素被构建后会立即调用的函数。当整个表单被构建后,最后将调用可选属性$form[‘#after_build’]中定义的函数。$form和$form_state将作为参数传递给所有的#after_build函数。Drupal核心中有一个实际例子,那就是在“管理站点配置文件系统”中,文件系统路径的显示。这里使用了一个#after_build函数(在这里就是system_check_directory()),用来判定目录是否存在或者是否可写,如果不存在或不可写,那么将为该表单元素设置一个错误消息。

 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

检查drupal表单是否已被提交

老葛的Drupal培训班 Think in Drupal

检查drupal表单是否已被提交
    如果你是按照图10-1所示的流程往下走的话,那么你将看到我们现在来到了一个分叉点。如果表单是初次显示的话,那么Drupal将会为其创建HTML。如果表单正被提交的话,那么Drupal将处理在表单中所输入的数据;我们稍后将会讨论这一点(参看本章后面的“验证表单”一节)。现在,我们将假定表单是初次显示。有一点非常重要,那就是不管表单是初次显示,还是正被提交,在此以前,它所走过的流程是一样的。
 
为表单查找一个主题函数
    如果$form['#theme']已被设置为了一个已有函数,那么Drupal将简单的使用该函数来负责表单的主题化。如果没有设置,那么主题注册表将查找一个对应于这个表单的表单ID的条目。如果存在这样的一个条目,那么就会将表单ID分配给$form['#theme'],在后面,当Drupal呈现表单时,它将基于表单ID来寻找主题函数。例如,如果表单ID为taxonomy_overview_terms,那么Drupal将调用对应的主题函数theme_taxonomy_overview_terms()。当然,可以在自定义主题中,使用主题函数或者模板文件来覆写这个主题函数;关于主题化的更多详细,可参看第8章。
 
允许drupal模块在表单呈现以前修改表单
    最后剩下的一件事,就是将表单从结构化的数据转化为HTML。但是在这以前,模块还有最后一个机会来调整表单。对于跨页面表单向导,或者需要在最后时刻修改表单的其它方式,这将会非常有用。此时将会调用$form['#pre_render']属性定义的任何函数,并将正被呈现的表单传递给这些函数。

Drupal版本:

呈现表单

老葛的Drupal培训班 Think in Drupal

为了将表单树从一个嵌套数组转化为HTML代码,表单构建器调用drupal_render()。这个递归函数将会遍历表单树的每个层次,对于每个层次,它将执行以下动作:
 
1. 判定是否定义了#children属性(这句话就是说,是否已经为该元素生成内容了);如果没有,那么按照以下步骤来呈现这个树节点的孩子:
 
    • 判定是否为这个元素定义了一个#theme函数。
 
    • 如果定义了,那么将这个元素的#type临时设置为markup(标识字体)。接着,将这个元素传递给主题函数,并将该元素重置为原来的样子。
 
    • 如果没有生成内容(可能是因为没有为这个元素定义#theme函数,或者因为调用的#theme函数在主题注册表中不存在,或者因为调用的#theme函数没有返回东西),那么逐个呈现这个元素的子元素(也就是,将子元素传递给drupal_render())。
 
    • 另一方面,如果#theme函数生成了内容,那么将内容存储在这个元素的#children属性中。
 
2. 如果表单元素本身还没有被呈现出来,那么调用这个元素所属类型的默认主题函数。例如,如果这个元素是表单中的一个文本字段(也就是说,在表单定义中,它的#type属性被设置为了textfield),那么默认主题函数就是theme_textfield()。如果没有为这个元素设置#type属性,那么默认为markup。核心元素(比如文本字段)的默认主题函数位于includes/form.inc中。
 
3. 如果为这个元素生成了内容,并且在#post_render属性中找到了一个或多个函数名字,那么将分别调用这些函数,并将内容和该元素传递过去。
 
4. 在内容前面添#prefix,在后面追加#suffix,并将它从函数中返回。
 
    这个递归迭代的作用就是为表单树的每个层次生成HTML。例如,一个表单了包含一个字段集,而字段集里面又包含两个字段,那么该字段集的#children属性将包含两个字段的HTML,而表单的#children属性将包含整个表单的HTML(其中包括字段集的HTML)。

    生成的HTML将会返回给drupal_get_form()的调用者。这就是呈现表单所要做的全部工作!我们到达了图10-1中的终点“返回HTML”

Drupal版本:

drupal表单验证

老葛的Drupal培训班 Think in Drupal

现在让我们回到图10-1,找到我们在“检查表单是否已被提交”一节中所提到的分叉点。现在让我们假定表单已被提交并包含了一些数据;这样我们将沿着另一分支前进,看看这种情况是怎么样的。使用以下两点来判定一个表单已被提交:$_POST不为空,$_POST['form_id']中的字符串匹配刚被构建的表单定义中的ID(参看“设置一个ID”一节)。如果这两点都满足了,那么Drupal 将开始验证表单。
    验证的目的是为了检查证被提交的数据的合理性。验证或者通过,或者失败。如果验证在某一点上失败了,那么将为用户重新显示这个表单,并带有错误消息。如果所有的验证都通过了,那么Drupal将对提交的数据进行实际的处理。
 
令牌验证
    在验证中首先检查的是,该表单是否使用了Drupal的令牌机制(参看 “设置一个令牌”一节)。使用令牌的所有Drupal表单,都会有一个唯一的令牌,它和表单一起被发送给浏览器,并且应该和其它表单值一同被提交。如果提交的数据中的令牌与表单构建时设置的令牌不匹配,或者令牌不存在,那么验证将会失败(尽管验证的其余部分也会继续执行,这样其它验证错误也会被标识出来)。
 
内置验证
    接着,检查必填字段,看用户有没有漏填的。检查带有#maxlength属性的字段,确保它没有超过最大字符数。检查带有选项的元素(复选框、单选按钮、下拉选择框),看所选的值是否是位于构建表单时所生成的原始选项列表中。
 
特定元素的验证
    如果为单个表单元素定义了一个#validate属性,那么将会调用这个属性所定义的函数,并将$form_state和$element作为参数传递过去。
 
验证回调
    最后,表单ID和表单值将被传递到表单的验证器函数中(函数名一般为:“表单ID”+ “_validate”)。

Drupal版本:

提交drupal表单

 

如果验证通过了,那么现在就应该把表单和它的值传递到一个函数中,该函数将做些实际的处理,以作为表单提交的结果。实际上,由于#submit属性可以包含一个数组,里面包含多个函数名字,所以可以使用多个函数来处理表单。调用数组中的每个函数,并向其传递参数$form和$form_state。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

重定向

老葛的Drupal培训班 Think in Drupal

用来处理表单的函数,应该把$form_state['redirect']设置为一个Drupal路径,比如node/1234,这样就可以将用户重定向到这个页面了。如果#submit属性中有多个函数,那么将会使用最后一个函数设置的$form_state['redirect']。如果没有函数把$form_state['redirect']设置为一个Drupal路径,那么用户将返回原来的页面(也就是,$_GET['q']的值)。在最后一个提交函数中返回FALSE,将会阻止重定向
    通过在表单中定义#redirect属性,就可以覆写在提交函数中$form_state['redirect']设置的重定向了,比如
 
$form['#redirect'] = 'node/1'
$form['#redirect'] = array('node/1', $query_string, $named_anchor)
 
    如果使用drupal_goto()中所用的参数术语,那么最后的一个例子将被改写为
 
$form['#redirect'] = array('node/1', $query, $fragment)
 
   表单重定向的判定,是由includes/form.inc中的drupal_redirect_form()完成的。而实际的重定向则由drupal_goto()实现,它为Web服务器返回一个Location头部。drupal_goto()的参数与后一个例子中的参数一致:drupal_goto($path = '', $query = NULL, $fragment = NULL)。
 

Drupal版本:

创建基本的drupal表单(1)

老葛的Drupal培训班 Think in Drupal

如果你曾经直接通过HTML创建过表单,那么在刚开始的时候,你可能会很不适应Drupal的这种方式。本节通过示例,帮你快速的创建自己的表单。为了起步,我们将创建一个简单的模块,让你用来输入自己的名字并将其输出到屏幕上来。我们将把它放在我们自己的模块里面,这样就不需要修改任何已有的代码了。我们的表单仅包含两个元素:文本输入字段和提交按钮。我们首先创建一个.info文件,输入以下内容:
 
; $Id$
name = Form example
description = Shows how to build a Drupal form.
package = Pro Drupal Development
core = 6.x

Drupal版本:

创建基本的drupal表单(2)

老葛的Drupal培训班 Think in Drupal

接下来,我们把实际的模块放在sites/all/modules/custom/formexample/formexample.module:
 
<?php
// $Id$
 
/**
 * @file
 * Play with the Form API.
 */
 
/**
 * Implementation of hook_menu().
 */
function formexample_menu() {
    $items['formexample'] = array(
        'title' => 'View the form',
        'page callback' => 'formexample_page',
        'access arguments' => array('access content'),
    );
    return $items;
}
 
/**
 * Menu callback.
 * Called when user goes to http://example.com/?q=formexample
 */
function formexample_page() {
    $output = t('This page contains our example form.');
 
    // Return the HTML generated from the $form data structure.
    $output .= drupal_get_form('formexample_nameform');
    return $output;
}
 
/**
 * 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)));
}

Drupal版本:

创建基本的drupal表单(3)

老葛的Drupal培训班 Think in Drupal

我们在这里实现了处理表单所需的基本函数:一个函数用于定义表单,一个用于验证,一个用于处理表单提交。另外,我们实现了一个菜单钩子和它对应的函数,这样就将一个URL和我们的函数关联起来了。我们的简单表单如图10-2所示:
 
10-2 一个基本表单,其中包含了一个文本输入框和一个提交按钮
 
    工作的重点就是填充表单的数据结构,或句话说,就是向Drupal描述表单。这一信息包含在一个嵌套的数组中,该数组描述了表单的元素和属性,它一般包含在一个名为$form的变量中。
    在前面的例子中,我们在formexample_nameform()中完成了定义表单这一重要任务,在这里我们为Drupal提供了显示表单所需要的最小信息。
 
注意 属性和元素有哪些区别呢?最基本的区别就是,属性没有属性,而元素可以有属性。提交按钮就是一个元素的例子,而提交按钮的#type属性就是一个属性的例子。你一眼便可以认出属性,这是因为属性拥有前缀“#”。我们有时把属性称为键,因为它们拥有一个值,为了得到该值,你必须知道相应的键。一个初学者常见的错误就是忘记了前缀“#”,此时,无论是Drupal还是你自己,都会感到非常困惑。如果你看到了错误消息“Cannot use string offset as an array in form.inc”,那么十有八九就是你忘记了字符“#”。
 

Drupal版本:

drupal表单属性

有些属性是通用的,而有些则特定于一个元素,比如一个按钮。对于属性的完整列表,可参看本章的最后部分。下面这个表单是比前面的例子中所给的表单要复杂一点:

 
$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>';
 
    #method属性的默认值为post,它可以被忽略。表单API不支持get方法,该方法在Drupal中也不常用,这是因为通过Drupal的菜单路由机制可以很容易的自动解析路径中的参数。#action属性定义在system_elements(),默认值为函数request_uri()的结果。通常与显示表单的URL相同。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

drupal表单IDs

老葛的Drupal培训班 Think in Drupal

Drupal需要有一些方式来唯一的标识表单,这样当一个页面有多个表单时,它就可以判定被提交的是哪一个表单,并且可以将表单与处理该表单的函数关联起来。为了唯一的标识表单,我们为每个表单分配了一个表单ID。在drupal_get_form()的调用中,所用的就是ID,如下所示:
 
drupal_get_form('mymodulename_identifier');
 
对于大多数表单,其ID的命名规则为:模块名字+一个表述该表单做什么的标识。例如,由用户模块创建的用户登录表单,它的ID为user_login。
Drupal使用表单ID来决定表单的验证、提交、主题函数的默认名字.另外,Drupal使用表单ID作为基础来为该特定表单生成一个<form>标签中的HTML ID属性,这样在Drupal中所有的表单都有一个唯一的ID。通过设置#id属性,你可以覆写该ID:
 
$form['#id'] = 'my-special-css-identifier';
    生成的HTML标签将会是这样的:
<form action="/path" "accept-charset="UTF-8" method="post"
id="my-special-css-identifier">
 
    表单ID作为名一个为form_id的隐藏域也嵌套在表单之中。在我们的例子中,我们选择formexample_nameform作为表单ID,这是因为它描述了我们的表单。从名称就可以看出,我们表单的目的是让用户输入他/她的名称。我们也可以将它命名为formexample_form,但是它的描述性不好----而且以后,我们可能还想再添加一个表单到我们的模块上。

Drupal版本:

drupal 字段集 fieldset(1)

 

很多时候,你想将你的表单划分到不同的字段集中---使用表单API可以很容易的做到这一点。每一个字段集都定义在表单的数据结构中,而它所包含的字段都定义为它的孩子。让我们向我们的例子中添加一个“喜欢的颜色”字段:
 
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;
}
 
    表单的显示结果如图10-3所示:
10-3 带有字段集的简单表单
老葛的Drupal 培训班 Think in Drupal

Drupal版本:

drupal 字段集 feildset(2)

老葛的Drupal培训班 Think in Drupal

我们使用可选的属性#collapsible和#collapsed来告诉Drupal,在点击第2个字段集标题时,通过使用Javascript让它可以伸缩。
  这里有个问题值得思考:当$form_state['values']传递到验证和提交函数时,颜色字段应该是$form_state['values']['color']['favorite_color'] 还是$form_state['values']['favorite_color']?换句话说就是,是否将该值嵌套在字段集里面?答案是:根据情况而定。默认情况下,在表单处理器中,表单值不用嵌套,所以下面的代码是正确的:
 
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)));
}
 
    更新后的提交处理器所设置的消息可以在图10-4中看到。
 
10-4 提交处理器为表单设置的消息
 
    然而,如果将属性#tree设为TRUE,那么表单的数据结构就会反映到表单值的名字中。所以,如果我们在定义表单时声明了:
 
$form['#tree'] = TRUE;
 
    那么我们就可以使用下面的方式访问数据了:
 
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)));
}
 
提示 将属性#tree设为TRUE,你将得到一个嵌套的表单值数组。将属性#tree设为FALSE(默认情况),你将得到一个未嵌套的表单值数组。
 

Drupal版本:

主题化drupal表单

老葛的Drupal培训班 Think in Drupal

Drupal拥有内置的函数,用来获取你定义的表单数据结构,并将其转换或者说是呈现为HTML。然后,许多时候你可能需要修改Drupal生成的输出,或者你可能想更好的控制该流程。幸运的是,在Drupal中,很容易实现这一点。
 
使用#prefix、#suffix和#markup
如果你的主题化需求非常简单,那么你就可以使用属性#prefix和#suffix在表单元素前面和/或后面添加HTML,从而满足需求:
 
$form['color'] = array(
    '#prefix' => '<hr />',
    '#title' => t('Color'),
    '#type' => 'fieldset',
    '#suffix' => '<div class="privacy-warning">' .
        t('This information will be displayed publicly!') . '</div>',
);
 
这一代码在颜色字段集上方添加了一条水平线,在其下方添加了一条私有消息,如图10-5所示。
 
10-5.#prefix和#suffix属性在一个元素前面和后面添加内容
 
你甚至可以在你的表单中把HTML标识文本声明为类型#markup(不过很少这样用)。任何不带#type属性的表单元素默认为markup类型。
 
$form['blinky'] = array(
    '#type' = 'markup',
    '#value' = '<blink>Hello!</blink>'
);
 
注意 这个向你的表单中引入了HTML标识文本的方法,一般认为与使用<blink>标签效果差不多。但是与编写一个主题函数相比,它不够干净利落,同时也增加了你网站设计人员的工作量。
 

Drupal版本:

使用主题函数

主题化表单的最灵活的方式,就是为表单或者表单元素使用一个特定的主题函数。这里涉及到了两个步骤。首先,Drupal需要知道我们的模块将实现哪些主题函数。这可以通过hook_theme()(详细请参看第8章)来完成。下面是我们模块的hook_theme()的一个快速实现,它主要说的是“我们的模块提供了两个主题函数,无须额外参数就可以调用它们”:

/**
 * Implementation of hook_theme().
 */
function formexample_theme() {
    return array(
        'formexample_nameform' => array(
            'arguments' => array(),
        ),
        'formexample_alternate_nameform' => array(
            'arguments' => array(),
        )
    );
}
 
    在默认情况下,Drupal会查找名为“‘theme_’+表单ID的名字”的主题函数。在我们的例子中,Drupal将在主题注册表中查找theme_formexample_nameform条目,由于我们在formexample_theme()中定义了它,所以Drupal能够找到它。将会调用下面的主题函数,而它的输出与Drupal的默认主题化完全一样:
 
function theme_formexample_nameform($form) {
    $output = drupal_render($form);
    return $output;
}
 
拥有我们自己的主题函数的好处是,我们可以按照我们的意愿对变量$output进行解析、混合、添加等操作。我们可以很快的将一个特定元素放在表单的最前面。例如在下面的例子中,我们把颜色字段集放在了最前面。
 
function theme_formexample_nameform($form) {
    // Always put the the color selection at the top.
    $output = drupal_render($form['color']);
 
    // Then add the rest of the form.
    $output .= drupal_render($form);
 
    return $output;
}
 老葛的Drupal培训班 Think in Drupal

Drupal版本:

告诉Drupal使用哪个主题函数

通过为一个表单声明#theme属性,你可以命令Drupal使用一个不匹配“‘theme_’+表单ID名字”格式的主题函数:

 
// Now our form will be themed by the function
// theme_formexample_alternate_nameform().
$form['#theme'] = 'formexample_alternate_nameform';
 
或者,你也可以让Drupal为一个表单元素使用一个专门的主题函数:
 
// Theme this fieldset element with theme_formexample_coloredfieldset().
$form['color'] = array(
    '#title' => t('Color'),
    '#type' => 'fieldset',
    '#theme' => 'formexample_coloredfieldset'
);
 
    注意,在前面的两种情况中,你在#theme属性中定义的函数必须是主题注册表中注册过的;也就是说,必须在一个hook_theme()实现中对其进行了声明。
 
注意 Drupal将在你设定的#theme属性的字符串前面添加前缀“theme_”,所以我们将#theme设置为formexample_coloredfieldset而不是theme_formexample_coloredfieldset,尽管后者是所要调用的主题函数的名字。为什么这样呢?请参看第8章。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

使用hook_forms()声明验证和提交函数

老葛的Drupal培训班 Think in Drupal

有时,你会遇到一种特殊的情况,你想让许多不同的表单共用一个验证或者提交函数。这叫做代码复用,在该种情况下,这是一个不错的想法。例如,在节点模块中,所有节点类型都共用该模块的验证和提交函数。那么我们就需要一种方式,将多个表单ID映射到验证和提交函数上。这就需要hook_forms()了。
Drupal取回表单时,它首先查找基于表单ID定义表单的函数(正因为这样,在我们的代码中,我们使用函数formexample_nameform())。如果找不到该函数,它将触发hook_forms(),该钩子函数在所有的模块中查找匹配的表单ID以进行回调。例如,在node.module中,使用下面的代码,将不同类型的节点表单ID映射到了同一个处理器上:
 
/**
 * Implementation of hook_forms(). All node forms share the same form handler.
 */
function node_forms() {
    $forms = array();
    if ($types = node_get_types()) {
        foreach (array_keys($types) as $type) {
            $forms[$type .'_node_form']['callback'] = 'node_form';
        }
    }
    return $forms;
}
 
在我们的例子中,我们也可以实现hook_forms(),以将其它表单ID映射到我们已有的代码上:
 
/**
 * Implementation of hook_forms().
 */
function formexample_forms($form_id, $args) {
    $forms['formexample_special'] = array(
        'callback' => 'formexample_nameform');
    return $forms;
}
 
    现在,如果我们调用drupal_get_form('formexample_special'),Drupal首先检查定义该表单的函数formexample_special()。如果它找不到这个函数,那么将会调用hook_forms(),这样Drupal就会看到我们将表单ID formexample_special映射到了formexample_nameform上,Drupal将调用formexample_nameform()来获得表单定义,接着,分别尝试调用formexample_special_validate()和formexample_special_submit()来进行验证和提交。

Drupal版本:

主题、验证、提交函数的调用次序

你已经看到,在Drupal中,有多个地方可以用来放置你的主题、验证、提交函数。拥有这么多的选项会让人选择,到底要选择哪个函数呢?下面是Drupal查找位置的总结,这里按先后顺序排列,对于一个主题函数,假定你使用基于PHPTemplate的名为bluemarine的主题,并且你正在调用drupal_get_form('formexample_nameform')。然而,这还取决于你的hook_theme()实现。

    首先,如果在表单定义中将$form['#theme']设置为了'foo':
 
1. themes/bluemarine/foo.tpl.php // Template file provided by theme.
2. formexample/foo.tpl.php // Template file provided by module.
3. bluemarine_foo() // Function provided theme.
4. phptemplate_foo() // Theme function provided by theme engine.
5. theme_foo() // 'theme_' plus the value of $form['#theme'].
 
    然而,如果在表单定义中没有设置$form['#theme']:
 
1. themes/bluemarine/formexample-nameform.tpl.php // Template provided by theme.
2. formexample/formexample-nameform.tpl.php // Template file provided by module.
3. bluemarine_formexample_nameform() // Theme function provided by theme.
4. phptemplate_formexample_nameform() // Theme function provided by theme engine.
5. theme_formexample_nameform() // 'theme_' plus the form ID.
 
    在验证期间,表单验证器的设置次序如下:
1. A function defined by $form['#validate']
2. formexample_nameform_validate // Form ID plus 'validate'.
 
    当需要查找处理表单提交的函数时,查找的次序如下:
1. A function defined by $form['#submit']
2. formexample_nameform_submit // Form ID plus 'submit'.
 

    注意,表单可以有多个验证和提交函数。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

编写一个验证函数

老葛的Drupal培训班 Think in Drupal

Drupal有一个内置的机制,能高亮的显示验证失败的表单元素,并为用户显示一条错误消息。检查一下示例中的验证函数,来看一下它是怎么工作的:
 
/**
 * 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.'));
    }
}
 
    注意form_set_error()的使用。当King Kong访问我们的表单,并用他的巨大的键盘键入他的姓名的时候,他将在页面顶部看到一条错误消息,而包含错误的字段也被高亮显示,这里用的是红色,如图10-6所示:
10-6 向用户指示验证失败
 
当然,他也有可能仅仅输入他的名字Kong。我们在这里仅仅拿他作为一个例子,来说明form_set_error()为我们的表单设置了一条错误消息,并使得验证失败。

验证函数应该专注于验证。一般来讲,它们不应该修改数据。然而,它们可以向$form_state数组添加一些信息,在下面的一节中,我们将看到这一点。

Drupal版本:

从验证函数中传递数据

老葛的Drupal培训班 Think in Drupal

从验证函数中传递数据
    如果你的验证函数做了大量的处理,而你又想把结果保存下来以供提交函数使用,那么有两种不同的方式。你可以使用form_set_value()或者使用$form_state。
 
使用form_set_value()传递数据
最正式的方式是,当你在表单定义函数中创建表单时,创建一个表单元素来隐藏该数据,然后使用form_set_value()存储该数据。首先,你需要创建一个用来占位的表单元素:
 
$form['my_placeholder'] = array(
    '#type' => 'value',
    '#value' => array()
);
接着,在你的验证程序中,你把数据保存起来:
 
// Lots of work here to generate $my_data as part of validation.
...
// Now save our work.
form_set_value($form['my_placeholder'], $my_data, $form_state);
 
然后你就可以在提交函数中访问该数据了:
 
// Instead of repeating the work we did in the validation function,
// we can just use the data that we stored.
$my_data = $form_values['my_placeholder'];
 
或者假定你想将数据转化为标准形式。例如,你在数据库中存有一列国家代码,你需要对它们进行验证,但是你的不讲道理的老板坚持----用户可以在文本输入框中键入国家名称。你需要在你的表单中创建一个占位表单元素,通过使用一种巧妙的方式对用户输入进行验证,这样你就可以同时将“The Netherlands”和 “Nederland”映射为ISO 3166 国家代码“NL”了。
 
$form['country'] = array(
    '#title' => t('Country'),
    '#type' => 'textfield',
    '#description' => t('Enter your country.')
);
 
// Create a placeholder. Will be filled in during validation.
$form['country_code'] = array(
    '#type' => 'value',
    '#value' => ''
);
在验证函数内部,你将国家代码保存到占位表单元素中:
 
// Find out if we have a match.
$country_code = formexample_find_country_code($form_state['values']['country']);
if ($country_code) {
    // Found one. Save it so that the submit handler can see it.
    form_set_value($form['country_code'], $country_code, $form_state);
}
else {
    form_set_error('country', t('Your country was not recognized. Please use
        a standard name or country code.'));
}
 
    现在,提交处理器就可以使用$form_state['values'] ['country_code']访问国家代码了。

Drupal版本:

使用$form_state传递数据

一个更简单一点的方式是使用$form_state存储该值。由于$form_state在验证和提交函数中都是通过引用传递的,所以在验证函数中,可以将数值存储在这里,而在提交函数中,就可以使用它了。最好在$form_state中加上你模块的命名空间,而不是仅仅使用一个键。

 
// Lots of work here to generate $weather_data from slow web service
// as part of validation.
...
// Now save our work in $form_state.
$form_state['mymodulename']['weather'] = $weather_data
 
    接着,你就可以在你的提交函数中访问该数据了:
// Instead of repeating the work we did in the validation function,
// we can just use the data that we stored.
$weather_data = $form_state['mymodulename']['weather'];
 

    你可能会问,“为什么不把该值存储在$form_state['values']中,这样不就和表单字段值保持一致了吗?”你说的这种方式也能工作,但是要记住,$form_state['values']是用来放置表单字段值的,而不是放置模块存储的随机数据。还记不记得,Drupal允许任意的模块将验证和提交函数附加在任意的表单上,因此你不能假定只有你的模块使用了表单状态,所以应该采用一种兼容的可预期的方式来存储数据。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

针对表单元素的验证

老葛的Drupal'培训班 Think in Drupal

一般情况下,一个表单使用一个验证函数。但是也可以为单个表单元素设置一个验证函数,这和整个表单的验证函数一样。为了实现这一点,我们需要将元素的属性#element_validate设置为一个数组,其中包含了验证函数的名字。表单数据结构中该元素分支的一份完整拷贝,将被作为验证函数的第一个参数。下面是一个专门用来说明这一点的示例,在这里我们强制用户在一个文本字段中只能输入香料(spicy)和糖果(sweet):
 
// Store the allowed choices in the form definition.
$allowed_flavors = array(t('spicy'), t('sweet'));
$form['flavor'] = array(
    '#type' => 'textfield',
    '#title' => 'flavor',
    '#allowed_flavors' => $allowed_flavors,
    '#element_validate' => array('formexample_flavor_validate')
);
    那么你表单元素的验证函数应该如下所示:
function formexample_flavor_validate($element, $form_state) {
    if (!in_array($form_state['values']['flavor'], $element['#allowed_flavors']))  {
        form_error($element, t('You must enter spicy or sweet.'));
    }
}
    在调用完所有表单元素的验证函数以后,仍需调用表单验证函数。
 
提示 在你的表单元素未通过验证,你希望为它显示一条错误消息时,如果你知道表单元素的名字,那么使用form_set_error(),如果你拥有表单元素本身,那么使用form_error()。后者对前者做了简单封装。
 

Drupal版本:

表单重新构建

 

在验证期间,你可能判定你没有从用户那里获取足够的信息。例如,你可能将表单数值放到一个文本分析引擎中进行检查,然后判定这一内容很有可能是垃圾信息。最后,你想重新显示表单(里面包含用户已输入的值),不过这次添加了一个CAPTCHA,用来证明这个用户不是一个机器人。通过在你的验证函数中设置$form_state['rebuild'],你就可以通知Drupal需要进行一次重构了,就像这样:
 
$spam_score = spamservice($form_state['values']['my_textarea'];
if ($spam_score > 70) {
    $form_state['rebuild'] = TRUE;
    $form_state['formexample']['spam_score'] = $spam_score;
}
 
    在你的表单定义函数中,你的代码应该包含如下所示的内容:
function formexample_nameform($form_id, $form_state = NULL) {
    // Normal form definition happens.
    ...
    if (isset($form_state['formexample']['spam_score']) {
        // If this is set, we are rebuilding the form;
        // add the captcha form element to the form.
        ...
    }
    ...
}

老葛的Drupal'培训班 Think in Drupal

Drupal版本:

编写提交函数

老葛的Drupal培训班 Think in Drupal

提交函数是表单通过验证后负责实际的表单处理的函数。只有在表单验证完全通过,并且表单没有被标记为重新构建时,它才会执行。提交函数通常需要修改$form_state['redirect']。
    如果在表单被提交以后,你想让用户跳转到另一页面,那么你就需要返回一个Drupal路径,也就是用户接下来要访问的路径:
 
function formexample_form_submit($form, &$form_state) {
    // Do some stuff.
    ...
    // Now send user to node number 3.
    $form_state['redirect'] = 'node/3';
}
 
    如果你有多个函数用来处理表单提交(参看本章前面的“提交表单”一节),只有最后一个设置$form_state['redirect']的函数返才拥有最后的发言权。可以通过在表单中定义#redirect属性来覆写提交函数的重定向(参看本章前面的“重定向用户”一节)。通常使用hook_form_alter()来实现这一点。
 
提示 $form_state['rebuild'] 标记也可以设置在提交函数中,就像验证函数中一样。如果设置了,那么所有的提交函数都将运行,但是所有的重定向值都将被忽略,而表单将使用提交了的值进行重构。在向一个表单中添加可选字段时,这一点非常有用。
 

Drupal版本:

使用hook_form_alter()修改表单

 

使用hook_form_alter(),你可以修改任何表单。你只需要知道表单的ID就可以了。这里有两种方式可以用来修改表单。
 
修改任意表单
    让我们修改一下登录表单,它位于用户登录区块和用户登录页面中。
 
function formexample_form_alter(&$form, &$form_state, $form_id) {
    // This code gets called for every form Drupal builds; use an if statement
    // to respond only to the user login block and user login forms.
    if ($form_id == 'user_login_block' || $form_id == 'user_login') {
        // Add a dire warning to the top of the login form.
        $form['warning'] = array(
            '#value' => t('We log all login attempts!'),
            '#weight' => -5
        );
        // Change 'Log in' to 'Sign in'.
        $form['submit']['#value'] = t('Sign in');
    }
}
    由于$form是通过引用传递过来的,所以在这里我们对表单定义拥有完全的访问权,并且可以做出任何我们想要的修改。在例子中,我们使用表单默认元素(参看本章后面的“标识文本”一节)添加了一些文本,并接着修改了提交按钮的值。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

drupal表单API属性

老葛的Drupal培训班 Think in Drupal

当在你的表单构建函数中构建一个表单定义时,数组中的键用来声明表单的信息。在下面部分中列出了最常用的键。表单构建器可以自动添加一些键。
 
表单根部的属性
下面所列的属性是特定于表单根部的。换句话说,你可以设置$form['#programmed'] = TRUE,但是如果你设置$form['myfieldset']['mytextfield'] [#programmed'] = TRUE那么对表单构建器来说没有任何意义。
 
#parameters
    该属性是一个数组,包含了传递给drupal_get_form()的原始参数。通过drupal_retrieve_form()可添加该属性。
 
#programmed
    这是一个布尔值属性,用来指示一个表单是以程序的方式来提交的, 比如通过drupal_execute()。如果在表单处理前设置了属性#post,那么可以使用drupal_prepare_form()来设置该属性。
 
#build_id
    该属性是一个字符串(MD5哈希)。#build_id用来标识一个特定的表单实例。它作为一个隐藏域放在表单中,通过使用drupal_prepare_form()来设置这个表单元素,如下所示:
$form['form_build_id'] = array(
    '#type' => 'hidden',
    '#value' => $form['#build_id'],
    '#id' => $form['#build_id'],
    '#name' => 'form_build_id',
);
 
#token
    这个字符串(MD5哈希)是一个唯一的令牌,每个表单中都带有它,通过该令牌Drupal能够判定一个表单是一个实际的Drupal表单,而不是一个恶意用户修改后的。
 
#id
    这个属性是一个由form_clean_id($form_id)生成的字符串,并且它是一个HTML ID属性。$form_id中的任何背对的括号对“][”,下划线“_”,或者空格’’都将被连字符替换,以生成一致的CSS ID。在Drupal的同一个页面中,该ID是唯一的.如果同一个ID出现两次(例如,同一个表单在一个页面显示了两次),那么就会在后面添加一个连字符和一个自增的整数,例如foo-form, foo-form-1, 和foo-form-2。
 
#action
    这个字符串属性是HTML表单标签的动作属性。默认情况,它是request_uri()的返回值。
 
#method
    这个字符串属性指的是表单的提交方法---通常为post。表单API是基于post方法构建的,它将不会处理使用GET方法提交的表单。关于GET 和POST的区别,可参看HTML规范。如果在某种情况下,你想尝试使用GET方法,那么你真正需要可能是Drupal的菜单API,而不是表单API。
 
#redirect
    该属性可以是一个字符串或者一个数组。如果是一个字符串,那么它是在表单提交以后用户想要重定向到的Drupal路径。如果是一个数组,该数组将作为参数被传递给drupal_goto(),其中数组中的第一个元素应该是目标路径(这将允许向drupal_goto()传递额外的参数,比如一个查询字符串)。
 
#pre_render
    该属性是一个数组,它包含了在表单呈现以前所要调用的函数。每个函数都被调用,并且#pre_render所在的元素将被作为参数传递过来。例如,设置$form['#pre_render'] = array('foo', 'bar') 将使Drupal先调用函数foo(&$form),然后调用bar(&$form)。如果#pre_render是设置在一个表单元素上的话,比如$form['mytextfield']['#pre_render'] = array('foo'),那么Drupal将调用foo(&$element),其中$element就是$form['mytextfield']。当你想在表单验证运行以后,呈现以前,使用钩子修改表单结构时,这个属性非常有用。如果想在验证以前修改表单,那么使用hook_form_alter()。
 
#post_render
    该属性是一个数组,它包含了一组函数,这些函数可对刚被呈现的内容进行修改。如果你设置了$form['mytextfield']['#post_render'] = array('bar'),那么你可以这样修改刚创建的内容:
function bar($content, $element) {
    $new_content = t('This element (ID %id) has the following content:',
        array('%id' => $element['#id'])) . $content;
    return $new_content;
}
 
#cache
    该属性控制着表单是否可被Drupal的一般缓存系统所缓存。对表单进行缓存意味着,在表单被提交时,它不需要再被重新构建。如果你想每次都重新构建表单的话,那么你可以设置$form['#cache'] = FALSE。

Drupal版本:

drupal表单元素

 

在本节中,我们将通过例子来展示内置的Drupal表单元素。
 
Textfield(文本字段)
       元素textfield的示例如下:
$form['pet_name'] = array(
    '#title' => t('Name'),
    '#type' => 'textfield',
    '#description' => t('Enter the name of your pet.'),
    '#default_value' => $user->pet_name,
    '#maxlength' => 32,
    '#required' => TRUE,
    '#size' => 15,
    '#weight' => 5,
    '#autocomplete_path' => 'pet/common_pet_names',
);
 
$form['pet_weight'] = array(
    '#title' => t('Weight'),
    '#type' => 'textfield',
    '#description' => t('Enter the weight of your pet in kilograms.'),
    '#field_suffix' => t('kilograms'),
    '#default_value' => $user->pet_weight,
    '#size' => 4,
    '#weight' => 10,
);
 
    表单元素的显示结果如图10-11所示
10-11元素textfield
 
    #field_prefix 和 #field_suffix属性是特定于文本字段的,它们在文本字段输入框的前面或者后面紧接着放置一个字符串。
    #autocomplete属性定义了一个路径,Drupal自动包含进来的JavaScript将使用jQuery向该路径发送HTTP请求。在前面的例子中,它将请求http://example.com/pet/common_pet_names。实际例子可以参看modules/user/user.pages.inc中的user_autocomplete()函数。
    文本字段元素的常用属性如下:#attributes, #autocomplete_path (默认为 FALSE), #default_value, #description, #field_prefix, #field_suffix,#maxlength (默认为128), #prefix, #required, #size (默认为60), #suffix, #title,#process(默认为form_expand_ahah),和 #weight。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

drupal表单元素: Password(密码)

老葛的Drupal培训班 Think in Drupal

该元素创建一个HTML密码字段,在这里用户的输入不直接显示(一般在屏幕上使用符号“·”代替)。user_login_block()中的示例如下:
$form['pass'] = array(
    '#type' => 'password',
    '#title' => t('Password'),
    '#maxlength' => 60,
    '#size' => 15,
    '#required' => TRUE,
);
 
    密码元素的常用属性如下:#attributes, #description, #maxlength, #prefix, #required, #size (默认为 60), #suffix, #title, #process(默认为form_expand_ahah),和#weight。出于安全原因,在密码元素中不使用#default_value属性。

 

Drupal版本:

drupal表单元素: Password with Confirmation(带确认的密码)

老葛的Drupal培训班 Think in Drupal

该元素创建两个HTML密码字段,并附加一个验证器来检查两个密码是否匹配。例如,在用户模块中,当用户修改他/她的密码时,用到了该元素:
$form['account']['pass'] = array(
    '#type' => 'password_confirm',
    '#description' => t('To change the current user password, enter the new
        password in both fields.'),
    '#size' => 25,
);
 

Drupal版本:

drupal表单元素: Textarea(文本域)

 

文本域元素的示例如下:
$form['pet_habits'] = array(
       '#title' => t('Habits'),
       '#type' => 'textarea',
       '#description' => t('Describe the habits of your pet.'),
       '#default_value' => $user->pet_habits,
       '#cols' => 40,
       '#rows' => 3,
       '#resizable' => FALSE,
       '#weight' => 15,
);
       文本域元素的常用属性如下:#attributes, #cols (默认为60) , #default_value, #description, #prefix, #required,#resizable, #suffix, #title, #rows (默认为5) , #process(默认为form_expand_ahah), 和 #weight。
    如果通过设置#resizable为TRUE,启用动态的文本域调整器,那么属性#cols的设置将不起作用。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

drupal表单元素: Select(下拉选择框)

老葛的Drupal培训班 Think in Drupal

一个来自于modules/statistics/statistics.admin.inc的下拉选择框元素的示例:
 
$period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800,
    259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval');
 
/* Period now looks like this:
    Array (
        [3600] => 1 hour
        [10800] => 3 hours
        [21600] => 6 hours
        [32400] => 9 hours
        [43200] => 12 hours
        [86400] => 1 day
        [172800] => 2 days
        [259200] => 3 days
        [604800] => 1 week
        [1209600] => 2 weeks
        [2419200] => 4 weeks
        [4838400] => 8 weeks
        [9676800] => 16 weeks )
*/
 
$form['access']['statistics_flush_accesslog_timer'] = array(
    '#type' => 'select',
    '#title' => t('Discard access logs older than'),
    '#default_value' => variable_get('statistics_flush_accesslog_timer',            259200),
    '#options' => $period,
    '#description' => t('Older access log entries (including referrer statistics)
        will be automatically discarded. (Requires a correctly configured
        <a href="@cron">cron maintenance task</a>.)', array('@cron' =>
        url('admin/reports/status'))),
);
 
    通过将属性#options定义为一个包含子菜单选项的关联数组,Drupal支持对下拉选项的分组,如图10-12所示。
 
$options = array(
    array(
        t('Healthy') => array(
            1 => t('wagging'),
            2 => t('upright'),
            3 => t('no tail')
        ),
    ),
    array(
        t('Unhealthy') => array(
            4 => t('bleeding'),
            5 => t('oozing'),
        ),
    ),
);
$form['pet_tail'] = array(
    '#title' => t('Tail demeanor'),
    '#type' => 'select',
    '#description' => t('Pick the closest match that describes the tail
        of your pet.'),
    '#options' => $options,
    '#multiple' => FALSE,
    '#weight' => 20,
);
 
    图10-12 使用分组的下拉选择框
 
    通过将#multiple属性设置为TRUE,可以启用多选。这也将改变$form_state['values']中的值,从一个字符串(例如,'pet_tail' = '2',假定在前面的例子中选择了upright)变为了一个数组(例如,pet_tail = array( 1 => '1', 2 => '2'),假定在前面的例子中同时选择了wagging 和upright)。
    下拉选择框元素的常用属性如下:#attributes, #default_value,#description, #multiple, #options, #prefix, #required, #suffix, #title, #process(默认为form_expand_ahah),和#weight.

Drupal版本:

drupal表单元素: Radio Buttons(单选按钮)

老葛的Drupal培训班 Think in Drupal

来自于modules/block/block.admin.inc的单选按钮元素的示例:
 
$form['user_vis_settings']['custom'] = array(
    '#type' => 'radios',
    '#title' => t('Custom visibility settings'),
    '#options' => array(
        t('Users cannot control whether or not they see this block.'),
        t('Show this block by default, but let individual users hide it.'),
        t('Hide this block by default but let individual users show it.')
    ),
    '#description' => t('Allow individual users to customize the visibility of
        this block in their account settings.'),
    '#default_value' => $edit['custom'],
);
       单选按钮元素的常用属性如下:#attributes, #default_value, #description,#options, #prefix, #required, #suffix, #title, 和 #weight.注意#process属性默认设为expand_radios() (参看 includes/form.inc)。
 

Drupal版本:

drupal表单元素: Check Boxes(复选框)

 

复选框元素的示例如下。该元素的呈现版本如图10-13所示。
$options = array(
    'poison' => t('Sprays deadly poison'),
    'metal' => t('Can bite/claw through metal'),
    'deadly' => t('Killed previous owner') );
$form['danger'] = array(
    '#title' => t('Special conditions'),
    '#type' => 'checkboxes',
    '#description' => (t('Please note if any of these conditions apply to your
        pet.')),
    '#options' => $options,
    '#weight' => 25,
);
10-13 复选框元素示例图
 
    在验证和提交函数中,通常使用array_filter()函数来获取复选框的键。例如,假如在图10-13中前两个复选框被选中了,那么$form_state['values']['danger']将包含以下内容:
array(
    'poison' => 'poison',
    'metal' => 'metal',
    deadly' => 0,
)
    运行array_filter($form_state['values']['danger'])将生成只包含复选框的键的数组:array('poison', 'metal')。
       复选框元素的常用属性如下:#attributes, #default_value, #description, #options, #prefix, #required, #suffix, #title, #tree (默认为TRUE), 和#weight.注意#process属性默认设为expand_checkboxes() (参看 includes/form.inc)。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

drupal表单元素: Value(值)

老葛的Drupal培训班 Think in Drupal

值元素是用来在drupal内部将数值从$form传递到$form_state['values']的,而不需要将其发送到浏览器端,例如:
$form['pid'] = array(
    '#type' => 'value',
    '#value' => 123,
);
    当表单提交后$form_state['values']['pid']将为123。
    不要混淆了type = '#value' 和 #value = 123。前者声明了正被描述的元素的类型,而后者声明了该元素的值。值元素只有属性#type和#value可用。

Drupal版本:

drupal表单元素: Hidden(隐藏域)

老葛的Drupal培训班 Think in Drupal

该元素使用一个类型为hidden的HTML输入字段将一个隐藏值传递到一个表单中,示例如下:
 
$form['my_hidden_field'] = array(
    '#type' => 'hidden',
    '#value' => t('I am a hidden field value'),
);
 
    如果你想在表单中传递一个隐藏值,通常使用值元素会更好一些,只有当值元素不能满足需求时才使用隐藏域元素。用户可以通过web表单的HTML源代码来查看隐藏域元素,但是却查看不了值元素,这是因为后者只存在于Drupal内部。
    隐藏域元素只有属性#type、#value、#prefix, #process(默认为form_expand_ahah), 和#suffix可用。
 

Drupal版本:

drupal表单元素: Date(日期)

老葛的Drupal培训班 Think in Drupal

日期元素,如图10-14所示,它是一个由3个下拉选择框联合而成的元素:
 
$form['deadline'] = array(
    '#title' => t('Deadline'),
    '#type' => 'date',
    '#description' => t('Set the deadline.'),
    '#default_value' => array(
        'month' => format_date(time(), 'custom', 'n'),
        'day' => format_date(time(), 'custom', 'j'),
        'year' => format_date(time(), 'custom', 'Y'),
    ),
);
 
10-14 日期字段
 
    日期元素的常用属性如下:#attributes, #default_value, #description, #prefix, #required, #suffix, #title, 和#weight. 属性#process默认设为expand_date(),在该方法中年选择器被硬编码为从1900到2050。属性#element_validate默认设为date_validate()(两个函数都位于includes/form.inc中)。当你在表单中定义日期元素时,通过定义这些属性,就使用你自己的代码来替代默认的了。

Drupal版本:

drupal表单元素: Weight(重量)

老葛的Drupal培训班 Think in Drupal

重量元素(不要与属性#weight混淆了)是一个用来声明重量的下拉选择框:
 
$form['weight'] = array(
    '#type' => 'weight',
    '#title' => t('Weight'),
    '#default_value' => $edit['weight'],
    '#delta' => 10,
    '#description' => t('In listings, the heavier vocabularies will sink and the
        lighter vocabularies will be positioned nearer the top.'),
);
 
前面代码的显示结果如图10-15所示。
 
10-15 重量元素
 
    属性#delta决定了重量的可供选择范围,默认为10.例如,如果你将#delta设为50,那么重量的范围就应该为从-50到50. 重量元素的常用属性如下:#attributes, #delta (默认为 10), #default_value, #description, #prefix, #required, #suffix, #title, 和#weight。#process属性默认为array('process_weight', 'form_expand_ahah')。
 

Drupal版本:

drupal表单元素: File Upload(文件上传)

老葛的Drupal培训班 Think in Drupal

文件元素创建了一个文件上传接口。下面是一个来自于modules/user/user.module的示例:
 
$form['picture']['picture_upload'] = array(
    '#type' => 'file',
    '#title' => t('Upload picture'),
    '#size' => 48,
    '#description' => t('Your virtual face or picture.')
);
 
    本元素的显示方式如图10-16所示。
 
10-16 文件上传元素
 
注意,如果你使用了文件元素,那么你需要在你表单的根部设置属性enctype:$form['#attributes']['enctype'] = 'multipart/form-data';
文件元素的常用属性如下:#attributes, #default_value, #description, #prefix, #required, #size (默认为 60), #suffix, #title, 和 #weight.
 

Drupal版本:

drupal表单元素: Fieldset(字段集)

老葛的Drupal培训班 Think in Drupal

字段集元素是用来对其它表单元素进行归类分组的。可将其声明为可伸缩的,这样当用户查看表单并点击字段集标题时,由Drupal自动提供的JavaScript能够动态的打开和关闭字段集。注意,在这个例子中,属性#access用来允许或拒绝访问字段集中的所有字段:
 
// Node author information for administrators.
$form['author'] = array(
    '#type' => 'fieldset',
    '#access' => user_access('administer nodes'),
    '#title' => t('Authoring information'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#weight' => 20,
);
 
字段集元素的常用属性如下:#attributes, #collapsed (默认为 FALSE), #collapsible (默认为 FALSE), #description, #prefix, #suffix, #title, #process(默认为form_expand_ahah),和 #weight。
 

Drupal版本:

drupal表单元素: Submit(提交按钮)

老葛的Drupal培训班 Think in Drupal

提交按钮元素是用来提交表单的。按钮内部显示的单词默认为“提交”,但是可以使用属性#value来修改它:
 
$form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Continue'),
);
 
    提交按钮元素的常用属性如下:#attributes, #button_type (默认为 'submit'), #executes_submit_callback (默认为 TRUE), #name (默认为 'op'),#prefix, #suffix, #value, #process(默认为form_expand_ahah),和#weight。
    另外,可以将#validate和#submit属性直接分配给提交按钮元素。例如,如果#submit设置为了array('my_special_form_submit'),那么就会使用函数my_special_form_submit()来替代表单的定义了的提交处理器。

Drupal版本:

drupal表单元素: Button(按钮)

 

按钮元素除了属性#executes_submit_callback默认为FALSE以外,其它属性与提交按钮元素完全相同。属性#executes_submit_callback告诉Drupal是否需要处理表单,为TRUE时处理表单,为FALSE时则简单的重新呈现表单。和提交按钮元素一样,可以将特定的验证和提交函数直接分配给这个按钮元素。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

drupal表单元素: Image Button(图片按钮)

老葛的Drupal培训班 Think in Drupal

图片按钮元素与提交按钮元素基本相同,但有两点例外。首先,它有一个#src属性,使用一个图片的URL作为它的值。其次,它把内部表单属性#has_garbage_value设置为了TRUE,这样就会阻止使用#default_value属性,从而避免在微软IE浏览器中的臭虫。不要在图片按钮中使用#default_value属性。下面是一个图片按钮,它使用内置的“Powered by Drupal”图片作为按钮:
 
$form['my_image_button'] = array(
    '#type' => 'image_button',
    '#src' => 'misc/powered-blue-80x15.png',
    '#value' => 'foo',
);
 
    通过查看$form_state['clicked_button']['#value'],就可以安全的取回图片按钮的值了。
 

Drupal版本:

drupal表单元素: Markup(标识文本)

老葛的Drupal培训班 Think in Drupal

如果没有设置属性#type的话,标识文本元素就是默认的元素类型了。它用来在表单中引入一段文本或者HTML。
 
$form['disclaimer'] = array(
    '#prefix' => '<div>',
    '#value' => t('The information below is entirely optional.'),
    '#suffix' => '</div>',
);
 
    标识文本元素的常用属性如下:#attributes, #prefix (默认为空字符串 ''), #suffix (默认为空字符串''), #value, and #weight.
 
警告 如果你把文本输出在一个可伸缩的字段集的内部,那么要使用<div>标签对其进行包装,如同例子中所展示的那样,这样当字段集被折叠起来时,你的文本也被折叠在了里面。
 

Drupal版本:

drupal表单元素: Item(项目)

 

项目元素的格式与其它输入表单元素的相同,比如文本字段或下拉选择框字段,但是它缺少输入框。
 
$form['removed'] = array(
    '#title' => t('Shoe size'),
    '#type' => 'item',
    '#description' => t('This question has been removed because the law prohibits       us from asking your shoe size.'),
);
 
前面元素的呈现结果如图10-17所示。
 
10-17 项目元素
 
       项目元素的常用属性如下:#attributes, #description, #prefix(默认为空字符串''), #required, #suffix (默认为空字符串''),#title, #value,和 #weight.

老葛的Drupal培训班 Think in Drupal

Drupal版本:

#ahah属性(1)

老葛的Drupal培训班 Think in Drupal

#ahah元素属性是向Drupal提供AHAH实现信息的,AHAH允许使用JavaScript来修改表单元素。
 
提示 你可能已经注意到了,在我们描述过的许多表单元素中,#process的默认值都为form_expand_ahah。在元素中添加#ahah属性,就是向Drupal指示为这个元素使用AHAH。函数form_expand_ahah()用来确保#ahah的值拥有合理的默认值。
 
    在上传模块的用于文件上传的附件按钮中,就用到了这一个属性,如下所示:
 
$form['new']['attach'] = array(
    '#type' => 'submit',
    '#value' => t('Attach'),
    '#name' => 'attach',
    '#ahah' => array(
    'path' => 'upload/js',
    'wrapper' => 'attach-wrapper',
    'progress' => array(
        'type' => 'bar',
        'message' => t('Please wait...'),
    ),
),
'#submit' => array('node_form_submit_build_node'),
);
 
    #ahah属性的值是一个键值数组。下面的键是必须的:
 
• path: JavaScript所要请求的菜单项的Drupal路径。菜单项的回调和菜单项的路径以js结尾,这表示该项目是通过JavaScript调用的。在前面的例子中,Drupal路径就是upload/js,而相应的回调就是upload_js()(不信的话,你可以查看modules/upload/upload.module中的函数upload_menu())。
 
• wrapper: 对应于一个HTML元素的id属性(通常为<div>)。在前面的例子中,上传模块涉及到下面的这个元素:<div id="attach-wrapper">。
 

Drupal版本:

drupal #ahah属性(2)

老葛的Drupal培训班 Think in Drupal

下面的键是可选的:
• effect: 在替换元素时使用的视觉效果。可能的值有none,fade,和slide。默认值为none。
 
• event: 事件,用来触发浏览器执行JavaScript HTTP请求。Drupal基于元素类型设置了一些默认值。这些值显示在表10-1中。
 
10-1.在表单元素中,触发AHAH的事件的默认名字
元素          默认事件
submit          mousedown*
button          mousedown*
image_button    mousedown*
password        blur
textfield       blur
textarea        blur
radio           change
checkbox        change
select          change
*还包括keypress事件。
 
• method: 当JavaScript HTTP请求的响应返回时,用来修改已有HTML的JQuery方法。可能的值有after,append, before, prepend, 和replace。默认的方法是replace。这个值用在下面的JavaScript(参看misc/ahah.js)中:
 
if (this.method == 'replace') {
    wrapper.empty().append(new_content);
}
else {
    wrapper[this.method](new_content);
}
 
• progress: 通知的方式-----一个JavaScript事件发生后,Drupal向用户发送通知的方式。该属性的值是一个数组,包含以下键:type和message,例如:
 
$form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Click Me'),
    '#ahah' => array(
        'event' => 'click',
        'path' => 'poof/message_js',
        'wrapper' => 'target',
        'effect' => 'fade',
        'progress' => array(
            'type' => 'throbber',
            'message' => t('One moment...'),
        ),
    )
);
 
    type的默认值为throbber,它是一个圆形的转动的图标,在JavaScript HTTP请求正在运行时,它还会显示一个可选的消息。另外的一个选择就是bar,它是一个进度条(如果声明为bar的话,那么将会添加一个单独的JavaScript文件misc/progress.js)。如果类型被设置为了bar,那么有以下可选键可用:url和interval。url键用来为进度条声明一个URL,通过调用这个URL,就可以判定它的百分比,一个从0到100的整数;而interval键则是用来声明检查进度的频率(以秒为单位)。
 
• selector: 通过声明一个选择器,就可以将JavaScript HTTP 请求的结果附加在页面中的多个元素上(而不仅仅是表单元素)。

Drupal版本:

drupal #ahah属性(3)

老葛的Drupal培训班 Think in Drupal

下面是一个表单的简单示例,它允许使用AHAH来动态替换一些文本。按钮使用throbber来指示用户应该继续等待,如图10-18所示。这里是sites/all/modules/custom/poof/poof.info:
 
; $Id$
name = Poof
description = Demonstrates AHAH forms.
package = Pro Drupal Development
core = 6.x
 
    而下面则是sites/all/modules/custom/poof/poof.module:
 
<?php
 
/**
 * Implementation of hook_menu().
 */
function poof_menu() {
    $items['poof'] = array(
        'title' => 'Ahah!',
        'page callback' => 'drupal_get_form',
        'page arguments' => array('poof_form'),
        'access arguments' => array('access content'),
    );
    $items['poof/message_js'] = array(
        'page callback' => 'poof_message_js',
        'type' => MENU_CALLBACK,
        'access arguments' => array('access content'),
    );
    return $items;
}
 
/**
 * Form definition.
 */
function poof_form() {
    $form['target'] = array(
        '#type' => 'markup',
        '#prefix' => '<div id="target">',
        '#value' => t('Click the button below. I dare you.'),
        '#suffix' => '</div>',
    );
    $form['submit'] = array(
        '#type' => 'submit',
        '#value' => t('Click Me'),
        '#ahah' => array(
            'event' => 'click',
            'path' => 'poof/message_js',
            'wrapper' => 'target',
            'effect' => 'fade',
        )
    );
 
    return $form;
}
 
/**
 * Menu callback for AHAH additions.
 */
function poof_message_js() {
    $output = t('POOF!');
    drupal_json(array('status' => TRUE, 'data' => $output));
}
 
10-18.点击按钮后,将会显示一个圆形的转动的throbber图标,之后将会进行基于AHAH的文本替换。
 

Drupal版本:

drupal #ahah属性(4)

 

在下面,还是这个模块,不过这次实现了进度条功能,而更新的频率为两秒一次(参看图10-19)。
 
警告 这个模块只是简单的说明了如何与进度条进行交互;在实际中,你应该报告的是实际任务完成的百分比。特别强调的一点是,你不要像示例中所给的那样,使用Drupal的持久化变量系统来存储和读取进度,这是因为多个用户同时运行表单时,这种方式就会出错。替代的方式是,你需要对数据库进行查询来获取已插入记录的百分比。
 
<?php
 
/**
 * Implementation of hook_menu().
 */
function poof_menu() {
    $items['poof'] = array(
        'title' => 'Ahah!',
        'page callback' => 'drupal_get_form',
        'page arguments' => array('poof_form'),
        'access arguments' => array('access content'),
    );
    $items['poof/message_js'] = array(
        'page callback' => 'poof_message_js',
        'type' => MENU_CALLBACK,
        'access arguments' => array('access content'),
    );
    $items['poof/interval_js'] = array(
       'page callback' => 'poof_interval_js',
       'type' => MENU_CALLBACK,
       'access arguments' => array('access content'),
    );
    return $items;
}
 
/**
 * Form definition.
 */
function poof_form() {
    $form['target'] = array(
        '#type' => 'markup',
        '#prefix' => '<div id="target">',
        '#value' => t('Click the button below. I dare you.'),
        '#suffix' => '</div>',
    );
    $form['submit'] = array(
        '#type' => 'submit',
        '#value' => t('Click Me'),
        '#ahah' => array(
            'event' => 'click',
            'path' => 'poof/message_js',
            'wrapper' => 'target',
            'effect' => 'fade',
           'progress' => array(
              'type' => 'bar',
              'message' => t('One moment...'),
              'interval' => 2,
              'url' => 'poof/interval_js',
           ),
        )
    );
 
    return $form;
}
 
/**
 * Menu callback for AHAH additions.
 */
function poof_message_js() {
    $output = t('POOF!');
    for ($i = 0; $i < 100; $i = $i + 20) {
       // Record how far we are.
       variable_set('poof_percentage', $i);
       // Simulate performing a task by waiting 2 seconds.
       sleep(2);
    }
    drupal_json(array('status' => TRUE, 'data' => $output));
}
 
/**
 * Menu callback for AHAH progress bar intervals.
 */
function poof_interval_js() {
    // Read how far we are.
    $percentage = variable_get('poof_percentage', 0);
    // Return the value to the JavaScript progress bar.
    drupal_json(array('percentage' => $percentage));
}
 
10-19.进度条显示了完成的百分比。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

drupal跨页面表单(1)

老葛的Drupal培训班 Think in Drupal

我们已经学习了简单的单页面表单。但是你可能需要,让用户跨越多个页面来填充一个表单,或者使用几个不同的步骤来输入数据。让我们创建一个简短的模块,用来说明跨页面表单技术,在该模块中通过3个单独的步骤来从用户那里收集3种成分(ingredient)。我们的方式是使用Drupal内置的表单存储箱(storage bin)传递数值。我们将模块命名为formwizard.module。当然,我们首先需要一个sites/all/modules/custom/formwizard.info文件:
 
; $Id$
name = Form Wizard Example
description = An example of a multistep form.
package = Pro Drupal Development
core = 6.x
 

Drupal版本:

drupal跨页面表单(3)

老葛的Drupal培训班 Think in Drupal

在这个简单模块中,我们需要注意几点。在我们的表单构建函数formwizard_multiform()中,我们有一个参数$form_state,用来提供表单的状态信息。让我们看一下整个流程。如果我们访问http://example.com/?q=formwizard, 我们得到初始的表单,如图10-7所示:
10-7 多步表单的初始步
 
    当我们点击“Next”按钮时,Drupal处理本表单的方式和其它表单一样:构建表单,调用验证函数,调用提交函数。但是如果我们还没有处于表单的最后一步,那么提交函数将会简单得返回。Drupal将会注意到存储箱$form_state['storage']中存放的数值,所以它将会再次调用表单构建函数,这次带了一个$form_state的拷贝。(我们也可以设置$form_state['rebuild']来进行重新构建,但是当$form_state['storage']中有值时就不再需要设置它了。)再次调用表单构建函数,并将$form_state传递给我们模块的formwizard_multiform(),该函数将通过查看$form_state['storage']['step']的值,来判定我们所处的步骤并构建相应的表单。我们将得到如图10-8所示的表单。
 
10-8 多步表单的第二步
   

Drupal版本:

使用drupal_execute()通过程序来提交表单

老葛的Drupal培训班 Think in Drupal

在网页浏览器中可以显示的任何表单,也都可以使用程序的方式来填充表单。让我们通过程序填充我们的名字和喜欢的颜色。
 
$form_id = 'formexample_nameform';
$form_state['values'] = array(
    'user_name' => t('Marvin'),
    'favorite_color' => t('green')
);
// Submit the form using these values.
drupal_execute($form_id, $form_state);
 
    要做的就是这些!简单的提供表单ID和表单的数值,然后调用drupal_execute()。
 
警告 大多数提交函数都假定,制造请求的用户就是提交表单的用户。当通过程序来提交表单时,你需要对此格外小心,因为此时这两种用户可能不同。
 

Drupal版本:

修改一个特定的表单

老葛的Drupal培训班 Think in Drupal

前面的方式不错,但是如果有很多模块来修改表单,而每个表单都传递给所有的hook_form_alter()实现,你的心里就开始犯嘀咕了。“这太浪费了吧,”你可能会想。“为什么不仅仅根据表单ID构造一个函数并调用它呢?”你想得不错。Drupal完全可以实现你的想法。下面的函数也将修改用户登录表单:
function formexample_form_user_login_alter(&$form, &$form_state) {
    $form['warning'] = array(
        '#value' => t('We log all login attempts!'),
        '#weight' => -5
    );
 
    // Change 'Log in' to 'Sign in'.
    $form['submit']['#value'] = t('Sign in');
}
 
    函数的名字是这样构造的:
modulename + 'form' + form ID + 'alter'
    例如
'formexample' + 'form' + 'user_login' + 'alter'
    将生成
formexample_form_user_login_alter
 
    在这个特定的情况下,第一种方式是比较好的,因为这里涉及到两个表单ID(位于http://example.com/?q=user的表单user_login,和显示在用户区块中的表单user_login_block)。
 

Drupal版本:

总结

 

读完本章后,你应该理解一下概念:
drupal表单API的工作原理
创建简单的drupal表单
使用主题函数修改表单外观
为表单或者独立的表单元素编写一个验证函数
编写一个提交函数并在表单处理完后进行重定向
修改已有的表单
编写跨页面drupal表单(多页面表单,或者多步表单);
你可以使用的表单定义属性和它们的含义
    Drupal中可用的表单元素(文本字段, 下拉选择框, 单选按钮, 复选框,等等
    如何在表单中使用基于AHAH的文本替换
 
    关于表单的更多信息,包括各种提示和技巧,参看Drupal参考手册http://drupal.org/node/37775

老葛的Drupal培训班 Think in Drupal

Drupal版本:

添加到所有表单元素上的属性

老葛的Drupal培训班 Think in Drupal

当表单构建器使用表单定义构建表单时,它需要保证每一个表单元素都要有一些默认设置。这些默认值在includes/form.inc的函数_element_info()中设置,但是可以被hook_elements()中的表单元素定义所覆写。
 
#description
    该字符串属性将添加到所有表单元素上,默认为NULL。通过表单元素的主题函数来呈现它。例如,一个文本字段的描述呈现在textfield的下面,如图10-2所示。
 
#required
    该布尔值属性将被添加到所有表单元素上,默认为FALSE。将它设为TRUE,如果表单被提交以后而字段未被完成时,Drupal内置的表单验证将抛出一个错误消息。还有,如果将它设为TRUE,那么就会为这个元素设置一个CSS类(参看includes/form.inc中的theme_form_element())
 
#tree
    该布尔值属性将被添加到所有表单元素上,默认为FALSE。如果将它设为TRUE,表单提交后的$form_state['values']数组将会是嵌套的(而不是平坦的)。这将影响你访问提交数据的方式。(参看本章中的“字段集”部分)。
 
#post
    该数组属性是原始$_POST数据的一个拷贝,它将被表单构建器添加到所有的表单元素上。这样,在#process 和 #after_build中定义的函数就可以基于#post的内容做出聪明的决定。
 
#parents
    该数组属性将被添加到所有表单元素上,默认为一个空数组。它在表单构建器的内部使用,以标识表单树中的父元素。更多信息,参看http://drupal.org/node/48643
 
#attributes
    该数组属性将被添加到所有表单元素上,默认为一个空数组,但是主题函数一般会填充该数组。该数组中的成员将被作为HTML属性添加进来。例如$form['#attributes'] = array('enctype' => 'multipart/form-data')。

Drupal版本:

跨页面表单(2)

接着,我们将编写实际的模块。该模块将显示两个页面:一个用来输入数据(我们将重复使用这一页面),一个最终页面,用来显示用户的输入以及对用户输入的致谢。这里是sites/all/modules/custom/formwizard.module:

 
<?php
// $Id$
 
/**
 * @file
 * Example of a multistep form.
 */
 
/**
 * Implementation of hook_menu().
 */
function formwizard_menu() {
    $items['formwizard'] = array(
        'title' => t('Form Wizard'),
        'page callback' => 'drupal_get_form',
        'page arguments' => array('formwizard_multiform'),
        'type' => MENU_NORMAL_ITEM,
        'access arguments' => array('access content'),
    );
    $items['formwizard/thanks'] = array(
        'title' => t('Thanks!'),
        'page callback' => 'formwizard_thanks',
        'type' => MENU_CALLBACK,
        'access arguments' => array('access_content'),
    );
 
    return $items;
}
 
/**
 * Form definition. We build the form differently depending on
 * which step we're on.
 */
function formwizard_multiform(&$form_state = NULL) {
    // Find out which step we are on. If $form_state is not set,
    // that means we are beginning. Since the form is rebuilt, we
    // start at 0 in that case and the step is 1 during rebuild.
    $step = isset($form_state['values']) ? (int)$form_state['storage']['step'] : 0;
 
    // Store next step.
    $form_state['storage']['step'] = $step + 1;
 
    // Customize the fieldset title to indicate the current step to the user.
    $form['indicator'] = array(
        '#type' => 'fieldset',
        '#title' => t('Step @number', array('@number' => $step))
    );
 
    // The name of our ingredient form element is unique for
    // each step, e.g. ingredient_1, ingredient_2...
    $form['indicator']['ingredient_' . $step] = array(
        '#type' => 'textfield',
        '#title' => t('Ingredient'),
        '#description' => t('Enter ingredient @number of 3.', array('@number' => $step))
    );
 
    // The button will say Next until the last step, when it will say Submit.
    $button_name = t('Submit');
    if ($step < 3) {
        $button_name = t('Next');
    }
    $form['submit'] = array(
        '#type' => 'submit',
        '#value' => $button_name
    );
 
    switch($step) {
        case 2:
            // Save ingredient in storage bin.
            $form_state['storage']['ingredient_1'] =
                $form_state['values']['ingredient_1'];
            break;
        case 3:
            // Add ingredient to storage bin.
            $form_state['storage']['ingredient_2'] =
                $form_state['values']['ingredient_2'];
    }
 
    return $form;
}
 
/**
 * Validate handler for form ID 'formwizard_multiform'.
 */
function formwizard_multiform_validate($form, &$form_state) {
    // Show user which step we are on.
    drupal_set_message(t('Validation called for step @step',
        array('@step' => $form_state['storage']['step'] - 1)));
}
 
/**
 * Submit handler for form ID 'formwizard_multiform'.
 */
function formwizard_multiform_submit($form, &$form_state) {
    if ($form_state['storage']['step'] < 4) {
    return;
    }
    drupal_set_message(t('Your three ingredients were %ingredient_1,                %ingredient_2, and %ingredient_3.', array(
            '%ingredient_1' => $form_state['storage']['ingredient_1'],
            '%ingredient_2' => $form_state['storage']['ingredient_2'],
            '%ingredient_3' => $form_state['values']['ingredient_3']
            )
        )
    );
 
    // Clear storage bin to avoid automatic form rebuild that overrides our redirect.
    unset($form_state['storage']);
 
    // Redirect to a thank-you page.
    $form_state['redirect'] = 'formwizard/thanks';
}
 
function formwizard_thanks() {
    return t('Thanks, and have a nice day.');
}

老葛的Drupal培训班 Think in Drupal

Drupal版本:

跨页面表单(4)

老葛的Drupal培训班 Think in Drupal

我们有证据证明验证函数的运行,因为它通过调用drupal_set_message()在屏幕上显示了一条消息。而且我们字段集的标题和文本输入框的描述也被恰当的设置了,这意味着用户到达了第2步。让我们继续。在如图10-9所示的表单中,我们将输入最后一个成分。
 
10-9 多步表单的最后一步
 
    注意,在第3步,我们将提交按钮的名字从“Next”改为了“Submit”。还有,当处理完成时,提交处理器可以将用户重定向一个新页面。现在,当我们点击提交按钮时,我们的提交处理器将识别出这就是第四步,与前面几步的简单返回有所不同,它将对数据进行处理。在这个例子中,我们仅仅调用了drupal_set_message(),这将在Drupal提供的下一个页面中显示用户输入的信息,并将用户重定向到formwizard/thankyou。结果页面如图10-10所示。
 
10-10 多步表单的提交处理器已经运行,而用户已被重定向到了formwizard/thankyou
 
    在前面的例子中,我们向你展示了多步表单工作原理的基本轮廓。除了在$form_state中使用存储箱以外,你的模块还可以将数据保存到隐藏域中从而将其传到下一步,你也可以在你的提交处理器中将其保存到数据库中,或者使用表单ID作为键将其保存到全局变量$_SESSION中。需要理解的重点是,表单构建函数将被继续调用,这是因为填充了$form_state['storage'],通过使用前面的方式增加$form_state['storage']['step'],验证和提交函数就能够聪明的决定要做什么了。

Drupal版本:

通用的drupal表单元素属性

老葛的Drupal培训班 Think in Drupal

本部分解释的属性适用于所有的表单元素。
#type
    该字符串声明了一个表单元素的类型。例如,#type = 'textfield'。表单根部必须包含声明#type = 'form'。
 
#access
    该布尔值属性用来判定是否将该表单元素显示给用户。如果表单元素有子表单元素的话,如果父表单元素的#access属性为FALSE的话,那么子表单元素将不显示。例如,如果表单元素是一个字段集,并且它的#access为FALSE,那么字段集里面的所有字段都不显示。
    #access属性可被直接设置为TRUE或FALSE,也可以设置为执行时返回TRUE或FALSE的函数。当表单定义被取回时,将会执行该函数。下面这个例子来自于Drupal的默认节点表单:
$form['revision_information']['revision'] = array(
    '#access' => user_access('administer nodes'),
    '#type' => 'checkbox',
    '#title' => t('Create new revision'),
    '#default_value' => $node->revision,
);
 
#process
    该属性是一个关联数组。在数组的每个条目中,函数名作为键,传递给函数的任何参数作为值。当构建表单元素时将调用这些函数,从而允许在构建表单元素时对该元素进行额外的操作。例如,在modules/system/system.module定义了checkboxes类型,在构建表单期间,将调用includes/form.inc里面的函数expand_checkboxes():
$type['checkboxes'] = array(
    '#input' => TRUE,
    '#process' => array('expand_checkboxes'),
    '#tree' => TRUE
);
还可参看本章中“收集所有可能的表单元素定义”部分中的例子。当#process数组中的所有的函数都被调用以后,将为每个表单元素添加一个#processed属性。
 
#after_build
    该属性是一个函数数组,在构建完表单元素以后它们将被立即调用。每个要被调用的函数都有两个参数:$form 和 $form_state。例如,如果$form['#after_build'] = array('foo', 'bar'),那么Drupal在表单元素构建完以后,分别调用foo($form, $form_state)和bar($form, $form_state)。一旦这些函数都被调用以后,Drupal在内部将为每个表单元素添加一个#after_build_done属性。
 
#theme
    该可选属性定义了一个字符串,当Drupal为该表单元素寻找主题函数时使用。例如,设置#theme = 'foo',Drupal将会在主题注册表中查找对应于foo的条目。参看本章前面的“为表单寻找主题函数”一节。
 
#prefix
    该属性是一个字符串,在表单元素呈现时,它将被添加到表单元素的前面。
 
#suffix
    该属性是一个字符串,在表单元素呈现时,它将被添加到表单元素的后面。
 
#title
    该字符串是表单元素的标题。
 
#weight
    该属性可以是一个整数或者小数。当呈现表单元素时,将根据它们的重量进行排序。重量小的元素将被放到前面,重量大的元素将被放到后面。
 
#default_value
    该属性的类型取决于表单元素的类型。对于输入表单元素,如果表单还没有被提交,那么它就是在该字段中所用的值。不要将它与表单元素#value混淆了。表单元素#value定义了一个内部表单值,尽管用户看不到它,但是它却定义在表单中,并出现在$form_state['values']中。
 

Drupal版本: