第二节 创建基本的表单

创建基本的表单
 
如果你有创建HTML表单的经验,那么Drupal在这方面的做法可能让你觉得有些奇怪。不过没关系,这一章的例子可以让你快速学会处理自己的私人表单。开始的时候,我们写一个简单的模块用来请求用户写入自己的名字并把输入结果显示在屏幕上。我们将在sites/
all/modules/custom/formexample文件夹中建立我们的私有模块,而不是去改写其他已经存在的模块。其.info文件内容如下:
 
; $Id$
name = Form example
description = Shows how to build a Drupal form.
package = Pro Drupal Development
core = 6.x
 
而与之对应的.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)));
}
 
这样我们就实现了提交表单所需要的基本函数:一个函数用来定义表单,一个用来检验,一个用来提交。另外,我们实现了一个菜单钩子函数,这样就可以为我们的函数添加上一个URL了。我们的表单应该像图10-2这样:
 
             10-2 一个带用来输入文本的带有提交按钮的基本表单
 
函数所做的主要工作就是建立表单的数据结构,实际上就是在向Drupal描述这个表单。这些描述性的信息定义了表单的元素和属性,并最终组成一个多维数组,存在变量$form中。
 
我们在formexample_nameform()函数中为Drupal用来显示此表单提供了一些最基本的信息,这是定义表单中最重要的环节。
 
Note:元素和属性之间有什么区别呢?它们之间最基本的区别是,元素可以有自己的属性,但属性却不能。关于元素有一个现成的例子,就是提交按钮。而其中可以看到#type是“提交按钮”这个元素的一个属性。你可以很直观的分辨处哪些是属性,因为它们前面总有“#”这个符号。我们有时候也把属性叫做“键”,因为它们有与之对应的“值”,想要取得这些值,就必须字段键的名字。一个初学者常范的错误就是忘记在属性名前加上“#”,这样的情况下不管是Drupal还是你自己都会有些混乱了。所以,当你看到报错信息“Cannot use string offset as an array in form.inc”,那就表明你可能忘记在某处加上“#”了。
 
表单的属性
有些属性可以用在任何地方,而有些只能在特定的环境下使用,比如添加一个按钮。在本章的最后,有一个完整的关于属性的列表,而在这里,我们只针对刚才的例子中所涉及到的进行一次详细些的解说:
 
$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,所以可以省略。Drupal中并不常常使用“getmethod”,而且这种方法也不被表单API系统支持,这是因为它太容易被菜单路径选择机制用来自动分析路径的参数。#action属性是在system_elements()中定义的,其默认值就是request_uri()函数的返回值。这也就是用显示表单的路径。
 
表单ID
 
为了在同一页面中的多个表单中选出我们要提交的那个,以及将某个函数指向那个我们需要它去处理的特定表单,Drupal需要某种方法来把表单区分开,这就是给每个表单指派一个ID。这个功能是通过调用drupal_get_form()函数实现的,如下:
 
drupal_get_form('mymodulename_identifier');
 
对于大多数的表单来说,ID习惯上就是模块名加上一个描述表单作用的标识符。例如,用户登录表单(user login form)是在user模块中定义的,所以它的ID就是user_login
 
Drupal用表单ID来判别此表单在默认情况下应该使用哪些函数来进行验证、提交、和主题化。另外,Drupal也基于这个ID来生成HTML代码中此表单与<form>标签想对应的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的话,显然就不太好,因为以后我们可能在这个模块中再添加其他的表单。
 
字段集
 
人们通常都会希望把自己的表单分割成不同的字段集,表单API可以很方便的做到这点。每个每一个字段集都以数据结构的形式定义,而它的子元素就是各个字段。让我们为刚才的例子加上一个favorite color字段吧:
 
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 添加了字段集之后的表单
 
我们使用#collapsible #collapsed这两个可选的属性来让DrupalJavaScript把第二个字段集做成可摺叠的,点击字段集的标题就可以控制摺叠与否。
 
这里需要思考一个问题:当$form_state['values']被传递给验证和提交函数时,它的color字段会是$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)));
}
 
Tip:将#tree设为TRUE可以使字段和它们的值以多维数组的形式出现,而如果其值是FALSE,那么字段和值在数据库中的逻辑结构都是平铺排列的。
 
表单的主题化
 
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>',
);
 
这些代码会在Color字段集的上方加一条水平线,然后在下方加上一条私人信息,如图10-5
 
               10-5 使用#prefix #suffix属性在元素前后添加内容
 
你也可以将#type属性声明为markup来在表单中添加HTML代码(虽然这并不常用)。而且,任何没有定义#type属性的表单都会被默认为markup
 
$form['blinky'] = array(
'#type' = 'markup',
'#value' = '<blink>Hello!</blink>'
);
 
Note:这种将HTML标签引入到表单中的做法和使用<blink>标签其实差不多,它们不像撰写主题函数那样简洁,而且通常会给你的网站的设计者带来麻烦。
 
使用主题函数
将表单主题化的最灵活的方法就是为特定的表单和元素使用主题函数。这里分两步进行:第一,Drupal需要得知我们的模块需要实现哪个主题函数,这是由hook_theme()实现的(细节参见第八章)。这里有一个简易的例子,基本告诉了Drupal“我们的模块提供了两个主题函数,而且调用的时候不需要额外的参数”:
 
/**
* 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的默认主题解释我们的表单并生成输出结果:
 
function theme_formexample_nameform($form) {
$output = drupal_render($form);
return $output;
}
 
有了自己的主题函数,我们就可以随心所欲的对输出结果(其实就是变量$output)进行分析、改写和添加了。通过使用如下代码,我们可以便捷的把color字段置于表单的顶部:
 
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应该使用哪个主题函数
通过为表单设置#theme属性,我们可以让Drupal使用theme_form_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'
);
 
在以上两种情况中,你所指定的主题函数必须都事先使用hook_theme()声明好,这样主题引擎才能找到他们
 
Notedrupal会自动为#theme属性对应的字符串加上theme_所以虽然我们指定的是formexample_coloredfieldset 而不是theme_formexample_coloredfieldset,这个函数在稍后的过程中仍然会被调用。置于为什么会这样,请阅读第8
 
使用hook_forms()来指定ValidationSubmission函数
 
有时你会遇到一种比较特别的情况,那就是几个表单会使用同一个验证或者提交函数。这就是所谓的代码复用,在某些情况下会非常的方便。比如,一个节点模块,将验证和提交所有的节点类型,这时我们就需要通过使用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() ,从而发现formexample_special()这个表单ID已经映射到了formexample_nameform上。接下来就是调用formexample_nameform()来定义表单,然后尝试调用formexample_special_validate()
formexample_special_submit()来完成验证和提交的工作。
 
主题、验证和提交函数的调用顺序
 
如你所见,有多次机会为Drupal指明主题、验证和提交函数。正因为选择如此多样化,所以必须对Drupal在何处以何种顺序搜寻主题函数做一个总结,而实际上,这取决于你hook_theme()的实现。我们假定的情况是你正在使用一个名为bluemarinePHPTemplate-based主题,并且正在调用drupal_get_form('formexample_nameform')
 
首先,如果$form['#theme']在表单定义过程中被设为‘foo’,则按照一下顺序搜寻:
 
1. themes/bluemarine/foo.tpl.php // theme提供的模板文件
2. formexample/foo.tpl.php // module提供的模板文件
3. bluemarine_foo() // 函数提供的主题
4. phptemplate_foo() // 主题引擎提供的主题函数
5. theme_foo() // 'theme_' 加上 $form['#theme']的值
 
而假如并没有设置$form['#theme']的值,就会是以下情况:
 
1. themes/bluemarine/formexample-nameform.tpl.php // themem提供的模板文件
2. formexample/formexample-nameform.tpl.php // module提供的模板文件
3. bluemarine_formexample_nameform() //theme提供的主题函数
4. phptemplate_formexample_nameform() //主题引擎提供的主题函数
5. theme_formexample_nameform() // 'theme_' 加上表单ID
 
在表单的验证过程中,验证器的优先顺序:
 
1. $form['#validate']指定的函数
2. formexample_nameform_validate // Form ID 加上 'validate'
 
表单提交过程中,提交函数的优先顺序:
 
1. $form['#submit']指定的函数
2. formexample_nameform_submit // Form ID 加上 'submit'
 
另外,要记住,表单允许使用多重的验证和提交函数。
 
撰写验证函数
 
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()函数的使用。当金刚(KingKong)访问我们的表单并在它那巨型键盘上输入它的姓名时,它会在页面顶部在页面顶部看到一条包含了它名字的红色高亮报错信息,就像图10-6这样:
 
                     10-6 向用户表明验证失败了
 
也许它该只输入它的名——Kong,才对吧,哈哈。不管怎么说,form_set_error()将为表单报使验证失败。
 
验证函数应该只负责验证,而不是像笼统意义上的那样去修改数据。不过却可以向$form_state 数组中添加数据。
 
从验证函数传递数据
如果你的验证函数做了大量的工作,而你想把这些结果保存下来供给提交函数使用,你有两种选择:使用form_set_value() 或者 $form_state
1使用form_set_value()
最正规的方法是在定义表单的时候建立一个隐性的表单元素,再通过使用form_set_value()来保存数据。所以,要先创建占位用的表单元素:
 
$form['my_placeholder'] = array(
'#type' => 'value',
'#value' => array()
);
 
然后,在验证过程中保存数据:
 
//做大量的工作,生成了数据变量$my_data
………………
//现在要记录我们的工作结果了
form_set_value($form['my_placeholder'], $my_data, $form_state);
 
然后就能在提交函数中读取这些数据了:
 
//这里就可以直接提取数据而不是重复提交函数中做过的工作了
$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_values['country_code']中的国家代码了。
 
2使用$form_state传递数据
使用$form_state保存数据是一种比较简易的方式。由于验证和提交函数都会调用$form_state 所以可以在这里保存一些公用数据。而将$form_state i写进你的模块的命名空间明显好于只建立一个键值:
 
//在验证过程中,从迟缓的网页服务中生成 $weather_data
…………
// 现在记录数据
$form_state['mymodulename']['weather'] = $weather_data
 
然后像下面这样调用数据:
 
//直接访问就行了,不用重做验证函数做的工资了
$weather_data = $form_state['mymodulename']['weather'];
 
你可能会问,为什么不把数据存在$form_state['values']中,和其他表单字段的值放在一起呢?。当然这样做也能行,但是请弄清这样一个概念,$form_state['values']是用来存储字段的值,而不是模块生成的随机数据。因为Drupal允许任意的模块对任意表单进行验证和提交,你不能确保你的模块是唯一的处理此表单的模块,所以使用一贯通用的方法保存数据才是正理。
 
特定表单元素的验证
通常验证函数是为表单准备的。但某些可能的情况下,为指定某个单独的表单元素进行特别的验证可能会好于验证整个表单。将元素的#element_validate设置成一个含有验证函数的名字的数组可以做到这点,一个完整的表单数据结构拷贝将作为第一个参数发送出去。这里有一个略显牵强的例子,我们将迫使用户在文本框里输入spicy 或者sweet
 
//在表单过程中将允许的选项保存下来
$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.'));
}
}
 
而在元素的验证函数完成工作之后,整个表单的验证函数依然会被调用。
 
Tip 当你知道表单元素的名字时请使用form_set_error(),当你能得到这个表单本身时就要使用form_error(),后者可以理解成前者的容器。
 
表单的重建
 
在验证过程中,你可能觉得没有获得足够的用户信息。比如,可能把表单获得的数据传递给一个文本分析引擎,结果发现得到的内容很有可能并不是你想要的信息。所以你就想再次显示表单(里面完整的保存着用户刚才填写的东西),不过增加了一个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) {
// 正常的表单定义
...
if (isset($form_state['formexample']['spam_score']) {
// 如果为真,则重建表单
//然后给表单加上CAPTCHA元素
...
}
...
}
 
撰写提交函数
 
在表单完全通过验证,并且没有被标明进行重建之后,提交函数会开始实际的处理工作。然后依$form_state['redirect']的改写情况做出行动。
 
如果你希望表单在提交之后,用户跳转到其他页面,则提交函数要像下面这样返回一个Drupal的路径:
 
function formexample_form_submit($form, &$form_state) {
// 做些填充工作
...
// 然后将用户导向编号是3的节点
$form_state['redirect'] = 'node/3';
}
 
如果你有复数的提交函数(参见本章前面的“提交表单”一节),则最后一个设置了$form_state['redirect']属性的会生效。而提交函数的重定向可以通过定义表单的#redirect属性进行复写(参见本章前面的“重定向用户”一节)。通常都是使用hook_form_alter()来做这些工作的。
 
Tip $form_state['rebuild']也可以在提交函数里设置,就像在验证函数里的使用方式一样。这样所有的提交工作都将照常进行,但是全部的重定向都将被忽视,而表单将使用提交过的数据进行重建。这在给表单条件可选字段的时候会用得着。
 
使用hook_form_alter()改写表单
 
有了hook_form_alter(),你只需知道表单的ID就可以对其进行任意改写。具体操作上有两种方式:
 
改写任意表单
让我们以改写用户登录区块和页面上的登录表单为例:
 
function formexample_form_alter(&$form, &$form_state, $form_id) {
// 代码将查询所有的Drupal组件;然后使用if语句达到只对登录表单生效的目的。
if ($form_id == 'user_login_block' || $form_id == 'user_login') {
// 在表单顶部发送报错信息
$form['warning'] = array(
'#value' => t('We log all login attempts!'),
'#weight' => -5
);
// 'Log in' 改成 'Sign in'.
$form['submit']['#value'] = t('Sign in');
}
}
 
因为$form是按引用传递,所以我们此时拥有全部的表单定义权限,可以对其进行随意修改。在这个例子中,我们利用表单的初始元素添加了一些文本到表单中,并以此改写了提交按钮的值。
 
改写某个特定表单
上一种方式十分有效,但如果有大量的模块改要写表单,每一个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
);
// 'Log in' 变成 'Sign in'.
$form['submit']['#value'] = t('Sign in');
}
 
函数名的组成规则是:
 
modulename + 'form' + form ID + 'alter'
 
例如:
 
'formexample' + 'form' + 'user_login' + 'alter'
 
合起来就是:
 
formexample_form_user_login_alter
 
在这个具体实例中,第一种方法更适用。因为可以调用两个表单IDuser_login表单在http://example.com/?q=user页面中,而user_login_block表单在user区块上)。
 
通过drupal_execute()函数以编程方式提交表单
 
任何网页中的表单都可以以编程方式填写,让我们以填写姓名和最喜欢的颜色为例:
 
$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()
 
Caution许多提交函数都会假定发出请求和提交表单的用户是同一个。但在使用编程的方式时你要清楚的意识到,此时二者不必相同。
 
多页表单
 
我们已经研究了简易的单页面表单。但是有时候需要用户填写包含数个页面的表单或者分几个阶段输入数据。让我们用一个短小精悍的模块实现一个多页表单,用来分三步向用户收集信息。我们使用Drupal内置的表单存储器来传递变量值。模块的名字是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
 
接下来就是模块本身的编码了。模块将显示两个页面:一个用来输入数据(我们已经多次使用这个功能了),另一个用来显示用户输入的信息,并对他们的使用表示感谢。代码如下:
 
<?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.');
}
 
关于这个简单的模块有几点要注意的地方。在表单的创建函数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_set_message()在屏幕上显示了一条信息。并且字段集的标题和字段的描述都是正确的,这表示用户正处于第二阶段。我们将完成最后一步,如图10-9
 
            10-9 多重表单的最后阶段
 
请注意,在第三阶段中,按钮不再是“next”,而是“read”。同时,在处理完成后,提交器会将用户引向新页面。现在,当我们按下提交按钮,提交器将意识到这是第四阶段,并且不再像前几阶段一样工作,而是开始处理数据。在此例中,我们仅仅调用了drupal_set_message(),在下一页面显示一条信息,并将用户重定向到formwizard/thankyou。如图10-10所示结果:
 
      10-10 多重表单提交器正在运行,用户被重定向到了formwizard/thankyou
 
 
前面的例子是在特定情况下大致说明了多重表单是如何工作的。模块可以将数据保存在隐藏字段,而不是$form_state中,并传递到下一阶段。你也可以改写提交其,将值保存在数据库中,或者使用表单的ID作为键保存在超级全局变量$_SESSION中。重要的是要明白$form_state['storage']的变化将导致表单创建函数继续被调用。而通过前面例子中那样的方式增加$form_state['storage']['step']的内容,可以让验证和提交函数正确的执行我们想要做的事情。

 

评论

非常感谢作者辛苦的翻译。不过在这个地方作者应该是打字错误了:
在'撰写提交函数' 当中的'Tip'中的最后一句话:'这在给表单条件可选字段的时候会用得着'
原文为:This can be useful for adding optional fields to a form
所以我想这样写是不是会更好一点: 这在给表单添加可选字段的时候会非常的有效

大哥,"使用主题"theming表单那里那个hook_theme钩子函数是不是你自已想出来的theming表单的方法啊,我怎么看英文版的都没有你那个的呢?

还有那个主题化的顺序,和英文版的原意差很远耶.你不能够按照自己的猜测去乱译啊,会搞死人的!

关于hook_theme(),在原文的位置见下图

我水平所限,翻译肯定是有谬误的。

但是也正因为水平所限,所以不敢对原文进行臆测和乱写,搞不明白的地方只是逐字逐句翻译而已。

关于主题化的顺序,解释的部分可能有一些问题,但顺序本身都是照搬原书。

另外此书有不同的版本,由于你说根本在原书中看不到hook_theme(),我想可能是你参照的版本和我的并不一致所造成的。

我是新手,拜读您的大作,收获颇丰,在此感谢你的辛劳,真诚的说一声,谢谢。

Also, Drupal has a lot of Blog systems agate features, such as built-in blog agreeable output, the RSS account feeds are everywhere, labels (Tag) functions, XML-RPC, etc., there are abounding new website technology, these accepted access blazon (Portal) agreeable administration system, is absolutely actual rare.
Ed Hardy
Ed Hardy Shoes
Ed Hardy clothes
Cheap Ed Hardy
Drupal arrangement development goals, mainly for social networking and broadcast agreeable management by design. If you wish to install an accession on several sites, but as well ancient administration and advancement these sites
Ed Hardy Womens Jeans
Ed Hardy Womens Knits
Ed Hardy Womens Swimwear
Ed Hardy Womens T-Shirts
Ed Hardy Bags
agreeable administration system, is absolutely actual rare, this as well represents Drupal arrangement is a actual avant-garde and technologically avant-garde website system. Interface is actual intuitive
Ed Hardy Belts
Ed Hardy Caps
Abercrombie Fitch