第6章 Form API

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

Drupal提供了一套表单API,用来生成、验证和处理HTML表单。表单API将表单抽象为一个关联数组,里面包含了各种属性和对应的值。在生成表单页面时,呈现引擎会在适当的时候将数组呈现出来。表单API为我们带来了很多好处,由于我们将表单表示成为了结构化的数组,所以我们可以添加、删除、重新排序、和修改表单。当我们想用一种干净的方式,来对其它模块创建的表单进行修改时,这会特别方便。此外,表单API还对表单操作进行了保护,从而能够有效的防止表单注入攻击。对于任意的表单,我们可以使用表单API为其添加附加的验证和处理函数。

 

    当然,表单API再给我们带来很多便利和灵活性的同时,也给我们带来了高昂的学习成本,除此以外,定制表单的外观,通常也是非常琐碎和费时的工作。

 

    我们在前面已经接触过Drupal的表单了,并且用到了hook_form_alterhook_form_FORM_ID_alter。对于表单、表单元素应该有了感性的认识。如果以前,就使用过Drupal6下面的表单API写过模块,那么接下来的内容,就不难理解了。

 

在本章中,我们将迎难而上。首先通过两个实际的例子,来学习Drupal表单API最后,我们列出来了Drupal核心自带的各种表单元素,及实例代码,并对呈现(render)API做了简单介绍。


Drupal版本:

1 两步表单

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

 

    让我们来看第一个实例,我们先介绍一下,实例的背景。我们想建立一个联系我们的表单页面,我们知道有两种方法可用,一是使用Drupal核心自带的contact模块,另一个就是使用webform模块,通常这两个模块可以满足大多数的情况。但是假如,客户希望用户提交联系我们表单的时候,先确认一下,确认无误以后再提交。这在实际中,也是常见的需求。此时,contact模块和webform模块就不大适用了。每个客户对于表单元素的需求也各不相同,所以此时我们可以选择通过定制自己的模块来实现。

 

    假定客户的具体需求是这样的,在联系表单页面,用户可以输入姓名、单位名称、电子邮件、电话号码,邮件正文、访问来源等信息,用户填写了这些信息后,提交表单,进入确认页面;在确认页面,用户可以检查填写的信息,如果信息有误,可以返回来修改,如果信息无误,正式提交;正式提交后,用户看到一个致谢页面,用来“感谢用户的来信”。


Drupal版本:

10 AJAX表单

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

 

    Drupal7中,不再使用“AHAH”术语了,而是采用更通用的大家已经习以为常的“Ajax”。有时候,错误的表述被一而再,再而三的使用以后,也就成为了标准,Ajax就是这样。在7里面,表单APIAJAX提供了进一步的支持,我们可以方便的创建Ajax表单,实现动态表单的效果,而不需要借助于第三方模块,这比Drupal6进步了很多。

 

    现在就让我们通过一个实例,来实际学习一下Ajax表单吧。我们在构建各种站点时,经常会遇到让用户选择他所在的地区。比如他的出生地,他的户口所在地,他的常住地址。当一个销售商品时,我们需要客户的配送地址。中国的地址信息,通常包含省、市、县这一信息,为了让网站更加友好,通常将省市县做成联动的表单。我们的这个例子,就是在Drupal中实现省市县三级联动。

 


Drupal版本:

11准备工作

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

   首先,我们先做一些准备工作。Drupal7中,分类术语被处理成了实体,与以前的版本相比,Drupal7下面的分类更好用了。我们不妨把省市县信息存储成为分类,这样以后用起来也更加方便。首先我们创建一个“地区”词汇表,如图所示。

图片1.png 

在里面添加一些省市县测试信息。

图片2.png 

    注意,这里面,长垣县在20116月以前,是隶属于新乡市的,201161日改为省辖县(市)了,不过这里还是把它放在了新乡市下面,长垣是我老家。题外话。北京市由于是直辖市,所以这里给了一个中间层“区/县”。

 

    实际的省市县信息,可以借助于第三方模块,比如feedsmigrate,导入到Drupal的分类系统中去。这里就不再详细介绍了。


Drupal版本:

12创建相关文件

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    接下来,就是创建我们的模块,不妨把这个模块叫做“shengshixian”,在sites/all/modules/custom目录下创建一个shengshixian目录,接着创建两个文件,shengshixian.infoshengshixian.module。我们向info文件添加以下内容:

 

name = 省市县

description = 中国省市县三级联动.

core = 7.x

 

    接着向module文件添加逻辑代码:

 

<?php

/**

 * @file

 * 省市县三级联动实例代码,

 */

 

 

/**

 * 实现钩子hook_menu().

 */

function shengshixian_menu() {

  $items['ssx'] = array(

'title' => '省市县',

    'page callback' => 'shengshixian_test_page',

    'access callback' => TRUE,

    'type' => MENU_CALLBACK,

  );

 

  return $items;

}

 

/**

 * 路径“ssx”页面的回调函数

 */

function shengshixian_test_page(){

$render_array = array();

//我们为这个页面设置标题

drupal_set_title('省市县三级联动');

//页面的正文为一个表单,注意drupal_get_form返回的不是html,需要使用drupal_render呈现一下。

$render_array['#markup'] .= drupal_render(drupal_get_form('shengshixian_test_form'));

//Drupal7的页面回调,返回的应该是一个数组,只有在退化形式下,才返回字符串。

return $render_array;

}

 

    我们首先建立一个测试菜单路径“ssx”,然后添加了它的回调函数shengshixian_test_page。这里强调一下,drupal_get_form返回的不是html,需要需要使用drupal_render呈现一下,这与Drupal6下的有所不同。现在我们来看看这个表单的逻辑:

 

/**

 * 表单shengshixian_test_form的构建函数

 */

function shengshixian_test_form($form, &$form_state){

  //设置省市县对应元素的默认值

    $default_sheng = !empty($form_state['values']['sheng']) ? $form_state['values']['sheng'] : '';

$default_shi = !empty($form_state['values']['shi']) ? $form_state['values']['shi'] : '';

$default_xian = !empty($form_state['values']['xian']) ? $form_state['values']['xian'] : '';


//构建省份的选项数组,首先设置了一个提示语

    $sheng_options = array(

  '' => '请选择省份',

);


//向数据库中查询省份信息,

$query_sheng = db_select('taxonomy_term_data','ttd')

->fields('ttd', array('tid', 'name'));


//因为省份是第一级术语,分类术语的父亲为0

$query_sheng->leftJoin('taxonomy_term_hierarchy', 'tth', 'ttd.tid = tth.tid ');

$query_sheng->condition('tth.parent', 0);


//需要确定术语所在的词汇表,就是我们在前面创建的地区

$query_sheng->leftJoin('taxonomy_vocabulary', 'tv', 'ttd.vid = tv.vid ');

$query_sheng->condition('tv.machine_name', 'diqu');


//按照tid排序,并执行

$result_sheng = $query_sheng->orderBy('tid')->execute();


//将返回的结果,进行迭代,为$sheng_options赋值。

foreach ($result_sheng as $record) {

  $sheng_options[$record->tid] = $record->name;

}

//省份表单元素

$form['sheng'] = array(    

  '#title' => t('请选择您所在的省份?'),    

  '#type' => 'select',    

  '#options' => $sheng_options,    

  '#default_value' => $default_sheng, 

    //#ajax属性数组  

  '#ajax' => array(      

    'callback' => 'shengshixian_sheng_callback',      

    'wrapper' => 'shi-wrapper-div',      

    'method' => 'replace',      

    'effect' => 'fade',    

  ),  

);


//构建市的选项数组,首先设置了一个提示语

$shi_options = array(

  '' => '请选择市',

);

//在省份不为空的情况下,取该省份下的所有的市

if(!empty($default_sheng)){


  //向数据库中查询术语信息,

$query_shi = db_select('taxonomy_term_data','ttd')

->fields('ttd', array('tid', 'name'));

//将其父术语限定在前面的省份的具体值上

$query_shi->leftJoin('taxonomy_term_hierarchy', 'tth', 'ttd.tid = tth.tid ');

$query_shi->condition('tth.parent', $default_sheng);

//由于省份信息里面,已经包含了词汇表信息,所以我们不再需要关联这个taxonomy_vocabulary表。

//$query_sheng->leftJoin('taxonomy_vocabulary', 'tv', 'ttd.vid = tv.vid AND tv.machine_name = :machine_name', array(':machine_name' => 'diqu'));


//按照tid排序,并执行

$result_shi = $query_shi->orderBy('tid')->execute();


//将返回的结果,进行迭代,为$shi_options赋值。

foreach ($result_shi as $record) {

$shi_options[$record->tid] = $record->name;

}

 

}

/*

//测试代码,中间测试的时候用的,这里保留了,开发模块所用到的测试代码很多,多数都已删除。

  $form['test'] = array(  

  '#markup' => '123456:'.$default_sheng 

);

*/

//表单元素市

$form['shi'] = array(    

  '#title' => t('请选择您所在的市?'),    

'#prefix' => '<div id="shi-wrapper-div">',    

'#suffix' => '</div>',

  '#type' => 'select',    

  '#options' => $shi_options,    

  '#default_value' => $default_shi,

    '#ajax' => array(      

    'callback' => 'shengshixian_shi_callback',      

    'wrapper' => 'xian-wrapper-div',      

    'method' => 'replace',      

    'effect' => 'fade',    

  ),      

);


//构建县的选项数组,首先设置了一个提示语

$xian_options = array(

  '' => '请选择县',

);

//在市不为空的情况下,取该市下的所有的县

if(!empty($form_state['values']['shi'])){


  //向数据库中查询术语信息,

$query_xian = db_select('taxonomy_term_data','ttd')

->fields('ttd', array('tid', 'name'));


//将其父术语限定在前面的市的具体值上

$query_xian->leftJoin('taxonomy_term_hierarchy', 'tth', 'ttd.tid = tth.tid AND tth.parent = :parent', array(':parent' => $form_state['values']['shi']));

$query_xian->condition('tth.parent', $default_shi);


//由于最前面省份信息里面,已经包含了词汇表信息,所以我们不再需要关联这个taxonomy_vocabulary表。

//$query_sheng->leftJoin('taxonomy_vocabulary', 'tv', 'ttd.vid = tv.vid AND tv.machine_name = :machine_name', array(':machine_name' => 'diqu'));


//按照tid排序,并执行

$result_xian = $query_xian->orderBy('tid')->execute();


//将返回的结果,进行迭代,为$xian_options赋值。

foreach ($result_xian as $record) {

$xian_options[$record->tid] = $record->name;

}

}


//表单元素县

$form['xian'] = array(    

  '#title' => t('请选择您所在的县/?'),    

'#prefix' => '<div id="xian-wrapper-div">',    

'#suffix' => '</div>',

  '#type' => 'select',    

  '#options' => $xian_options,    

  '#default_value' => $default_xian,

    

);


  //提交按钮

  $form['submit'] = array(

    '#type' => 'submit',

    '#value' => t('提交'),

  );

  return $form;

}

 

/**

 * 表单元素sheng,它的值变更时,对应的Ajax回调函数。

 */

function shengshixian_sheng_callback($form,&$form_state){

  //根据当前省份,重新确定市的可选项。返回重新构建的表单元素shi

return $form['shi'];

}

 

/**

 * 表单元素sheng,它的值变更时,对应的Ajax回调函数。

 */

function shengshixian_shi_callback($form,&$form_state){

  //根据当前所选的市,重新确定县的可选项。返回重新构建的表单元素xian

return $form['xian'];

}

  

    对于上面的代码,对于从分类相关表中读取对应的省市县信息的代码,里面有详细的注释,相关的数据库操作,可参看数据库API一章。对于省市县对应的三个表单元素,这里都选用了下拉选择框。


Drupal版本:

13Ajax表单的三个关键要点

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    这里值得我们学习的地方有三点,一个是

$default_sheng = !empty($form_state['values']['sheng']) ? $form_state['values']['sheng'] : '';

$default_shi = !empty($form_state['values']['shi']) ? $form_state['values']['shi'] : '';

$default_xian = !empty($form_state['values']['xian']) ? $form_state['values']['xian'] : '';

 

还有一个是:

'#ajax' => array(      

'callback' => 'shengshixian_sheng_callback',      

'wrapper' => 'shi-wrapper-div',      

'method' => 'replace',      

'effect' => 'fade',    

), 

 

'#ajax' => array(      

'callback' => 'shengshixian_shi_callback',      

'wrapper' => 'xian-wrapper-div',      

'method' => 'replace',      

'effect' => 'fade',    

),

最后便是Ajax的回调函数:

 

function shengshixian_sheng_callback($form,&$form_state){

  //根据当前省份,重新确定市的可选项。返回重新构建的表单元素shi

return $form['shi'];

}

 

function shengshixian_shi_callback($form,&$form_state){

  //根据当前所选的市,重新确定县的可选项。返回重新构建的表单元素xian

return $form['xian'];

}

 

    这三处,是Ajax表单的关键所在。对于第一处,获取默认的表单元素的值,看似非常普通,但确实是一个关键的地方。我们先来描述一下大致的执行流程。当我们访问页面ssx时,我们看到了一个普通的表单。如图所示:


Drupal版本:

14地区三级联动

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

图片1.png

Drupal版本:

15 Ajax表单流程分析

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    此时,省份的可选项有“北京市”、“河南省”。市的可选项为空,县的可选项为空。也就是初次访问这个页面时,$default_sheng$default_shi$default_xian的值都为空。

    接着,当我们在表单页面选择“河南省”的时候,系统进行了Ajax调用,如图所示:

图片1.png 

    我们看到这里出现了“请稍等”的提示,表明系统进行了Ajax调用。聪明的读者会想到,Ajax回调应该就是hengshixian_sheng_callback了,这样理解是对的,但是还要再补充一点,那就是Drupal在后端重新构建了整个表单,在构建完整个表单后,根据hengshixian_sheng_callback回调函数,向浏览器端只返回变化的元素$form['shi'],浏览器端根据服务器端的返回值,重新设置了 “市”这一表单元素。

    在重新构建整个表单的过程中,此时$default_sheng的值,就是“河南省”的tid了,已经不再为空,所以此时“市”的可选项就不再为空了。

    当我们选择“新乡市”,系统再次进行了Ajax调用,如图所示:

图片2.png 

    此时,Drupal在后端再次重新构建了整个表单,在构建完整个表单后,根据shengshixian_shi_callback回调函数,向浏览器端返回更新过的元素$form['xian'],浏览器端根据服务器端的返回值,重新设置了 “县”这一表单元素。

   在重新构建整个表单的这次过程中,此时$default_sheng的值,就是“河南省”的tid了,此时“市”的可选项不为空。此时$default_shi的值,就是“新乡市”的tid了,所以此时“县”的可选项就不再为空了。如图所示:

图片3.png 

 

    当我们做完这样的流程分析以后,我们再来理解这段代码:

'#ajax' => array(      

'callback' => 'shengshixian_sheng_callback',      

'wrapper' => 'shi-wrapper-div',      

'method' => 'replace',      

'effect' => 'fade',    

),

    'callback'对应的是回调函数,通常这个回调函数非常简单,返回表单中的特定部分就可以了,注意这里是系统重新构建了整个表单后;'wrapper'表示动态更新的部分,这里对应于'#prefix' => '<div id="shi-wrapper-div">',也就是表单元素“市”的主内容;'method'表示对wrapper中内容的处理方法,可选值有'replace' (默认) 'after' 'append' 'before''prepend',这里是'replace''effect'表示Ajax特效,可选值有'none' (默认) 'fade''slide'

 

    最后,我们做一下归纳与总结,Drupal 7 中的Ajax表单,用起来非常简单,不需要编写自己的JQuery代码,回调函数也极其简单,只要我们遵守Drupal的规范,其余的工作都有Drupal替我们完成。从本质上来讲,这里的Ajax表单,就是多步表单的一个特例,我在前面,多次强调,重新构建整个表单,就是为了突出它其实就是一个多步表单。

 

    构建Ajax表单,需要注意三点:一是为带有ajax属性的元素提前设置默认值,二是正确的设置表单元素的ajax属性,三是编写ajax的回调函数。

 

    有关表单元素Ajax属性的更多信息,可参考http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html#properties


Drupal版本:

16 表单元素

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html#properties

 

    在本节中,我们将通过例子来学习Drupal核心自带的表单元素。表单元素类型定义在

hook_element_info中,Drupal核心表单元素大部分都定义在systemk_element_info。使用element_info($type)函数可以查看元素类型的默认属性。为方便大家查阅,这里按照字母顺序排列。


Drupal版本:

17 Actions(动作)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    它是一个包装器(wrapper)元素,用来对其它按钮元素进行归类分组。把'actions'元素作为数组的键,可以方便设置主题样式,同时也方便其它模块修改该表单的动作。

 

示例代码,来自(node.admin.inc): 

$form['filters']['status']['actions'] = array(

    '#type' => 'actions',

    '#attributes' => array('class' => array('container-inline')),

  );

  $form['filters']['status']['actions']['submit'] = array(

    '#type' => 'submit',

    '#value' => count($session) ? t('Refine') : t('Filter'),

  );

 

常用属性: #access #after_build#attributes #children #id#parents#post_render #pre_render #prefix #process #states #suffix #theme#theme_wrappers #tree #type#weight


Drupal版本:

18Button(按钮)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

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

 

示例代码,来自(simpletest\tests\form_test.module):

 

  $form['continue_button'] = array(

    '#type' => 'button',

    '#value' => 'Reset',

    // Rebuilds the form without keeping the values.

  );

 

常用属性: #access #after_build #ajax #attributes #button_type (默认为submit) #disabled #element_validate #executes_submit_callback ((默认为FALSE) #limit_validation_errors #name ((默认为op) #parents #post_render #prefix#pre_render#process #submit #states #suffix #theme #theme_wrappers #tree #type #validate #value #weight

 


Drupal版本:

19Checkbox(复选框)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

单个复选框。

示例代码,来自(node.pages.inc):

 

$form['revision_information']['revision'] = array(

    '#type' => 'checkbox',

    '#title' => t('Create new revision'),

    '#default_value' => $node->revision,

    '#access' => user_access('administer nodes'),

  ); 

 

常用属性: #access、 #after_build、 #ajax、 #attributes、 #default_value、 #description、 #disabled、 #element_validate、 #field_prefix、 #field_suffix、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #required、 #return_value (默认为 1)、 #states、 #suffix、 #theme、 #theme_wrappers、 #title、 #title_display (默认为after)、 #tree、 #type、 #weight

 


Drupal版本:

20Checkboxes(复选框)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    一组复选框。属性#options是一个关联数组,它的键是复选框的#return_value,对应的值则是用来显示给用户的。#options数组的键不能为0,假如为0了,那么系统就无法判断是否选中了这个选项。

 

示例代码,来自(node.module):

 

    $types = array_map('check_plain', node_type_get_names());

    $form['advanced']['type'] = array(

      '#type' => 'checkboxes',

      '#title' => t('Only of the type(s)'),

      '#prefix' => '<div class="criterion">',

      '#suffix' => '</div>',

      '#options' => $types,

    );

    在验证和提交函数中,通常使用array_filter()函数来获取复选框的键。在上述代码对应的验证函数中,就使用了array_filter()。代码如下:

 

$form_state['values']['type'] = array_filter($form_state['values']['type']);

 

常用属性: #access、 #after_build、 #ajax、 #attributes、 #default_value、 #description、 #disabled、 #element_validate、 #options、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #required、 #states、 #suffix、 #theme、 #theme_wrappers、 #title、 #title_display、 #tree (默认为TRUE)、 #type、 #weight


Drupal版本:

21 Container(容器)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    一个html容器,用来封装子元素。把子元素放在一个<div>中,并可以为这个div设置类或ID属性。

 

 

示例代码,来自(user.admin.inc):

 

  $form['filters']['status'] = array(

    '#type' => 'container',

    '#attributes' => array('class' => array('clearfix')),

    '#prefix' => ($i ? '<div class="additional-filters">' . t('and where') . '</div>' : ''),

  );

  $form['filters']['status']['filters'] = array(

    '#type' => 'container',

    '#attributes' => array('class' => array('filters')),

  );

  foreach ($filters as $key => $filter) {

    $form['filters']['status']['filters'][$key] = array(

      '#type' => 'select',

      '#options' => $filter['options'],

      '#title' => $filter['title'],

      '#default_value' => '[any]',

    );

  }

 

常用属性: #access、 #after_build、 #attributes #children、 #id、 #parents、 #post_render、 #pre_render、 #prefix、 #process、 #states、 #suffix、 #theme、 #theme_wrappers、 #tree、 #type、 #weight

 



Drupal版本:

22 Date(日期)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    日期元素,它是一个由3个下拉选择框联合而成的元素。如果没有提供默认值,#default_value则默认为今天的日期。#default_value#return_value的格式是一个包含三个元素的关联数组,对应的键为:'year'month''day'。例如:

array('year' => 2011, 'month' => 7, 'day' => 26)

 

示例代码,来自(profile.module):

 


$fields[$category][$field->name] = array(
  '#type' => 'date', 
  '#title' => check_plain($field->title), 
  '#default_value' => $edit[$field->name], 
  '#description' => _profile_form_explanation($field), 
  '#required' => $field->required
);

    Date元素默认的显示顺序是“月,日,年”,对于中文用户来说,这个顺序不是很习惯,要把它改为“年,月,日”,只需要在“admin/config/regional/date-time”页面,简单的配置一下默认的日期格式就可以了,将其设置为中文用户习惯的日期格式,比如“Y/m/d H:i”。

 

常用属性: #access、 #after_build、 #attributes、 #default_value、 #description、 #disabled、 #element_validate、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #required、 #states、 #suffix、 #theme、 #theme_wrappers、 #title、 #title_display、 #tree、 #type、 #weight

    属性#process默认设为form_process_date (),在该方法中年选择器被硬编码为从1900到2050。属性#element_validate默认设为date_validate()(两个函数都位于includes/form.inc中)。当你在表单中定义日期元素时,通过定义这两个属性,就可以使用你自己的代码来替代默认设置了。


Drupal版本:

23 fieldset(字段集)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    字段集元素是用来对其它表单元素进行归类分组的。可将其声明为可伸缩的,这样当用户查看表单并点击字段集标题时,由Drupal自动提供的JavaScript能够动态的打开和关闭字段集。

 

示例代码,来自(node.module):

 

  $form['visibility']['node_type'] = array(

    '#type' => 'fieldset',

    '#title' => t('Content types'),

    '#collapsible' => TRUE,

    '#collapsed' => TRUE,

    '#group' => 'visibility',

    '#weight' => 5,

  );

 

常用属性: #access、 #after_build、 #attributes、 #collapsed (默认为FALSE)、 #collapsible (默认为FALSE)、 #description、 #element_validate、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #states、 #suffix、 #theme、 #theme_wrappers、 #title、 #title_display、 #tree、 #type、 #weight

 


Drupal版本:

24 File(文件)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

 

创建了一个文件上传字段。注意,如果你使用了文件元素,那么你需要在你表单的根部设置属性enctype:

$form['#attributes']['enctype'] = 'multipart/form-data';

    代码enctype="multipart/form-data"在浏览器处理文件时是必须的。

 

示例代码,来自(user.module):

 

 ['picture']['picture_upload'] = array(

    '#type' => 'file',

    '#title' => t('Upload picture'),

    '#size' => 48,

    '#description' => t('Your virtual face or picture. Pictures larger than @dimensions pixels will be scaled down.', array('@dimensions' => variable_get('user_picture_dimensions', '85x85'))) . ' ' . filter_xss_admin(variable_get('user_picture_guidelines', '')),

  );

 

常用属性: #access、 #after_build、 #array_parents、 #attached、 #attributes、 #description、 #disabled、 #element_validate、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #required、 #size (默认为60)、 #states、 #suffix、 #theme、 #theme_wrappers、 #title、 #title_display、 #tree、 #type、 #weight


Drupal版本:

25 hidden(隐藏域)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    该元素把值存放在了一个隐藏的表单字段中。注意,如果你想使用JavaScript来修改隐藏元素的值,此时应该使用#default_value属性,而不是#value属性。

示例代码,来自(node.admin.inc):

 

    $form['nodes'][$nid] = array(

      '#type' => 'hidden',

      '#value' => $nid,

      '#prefix' => '<li>',

      '#suffix' => check_plain($title) . "</li>\n",

    );

 

常用属性: #access、 #after_build、 #ajax、 #default_value、 #element_validate、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #states、 #suffix、 #theme、 #theme_wrappers、 #tree、 #type、 #value、 #weight


Drupal版本:

26 mage_button(图片按钮)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    图片形式的表单提交按钮。它与提交按钮元素属性基本相同,但有两点例外。首先,它有一个#src属性,使用一个图片的URL作为它的值。其次,它把内部表单属性#has_garbage_value设置为了TRUE,这样就会阻止使用#default_value属性,从而避免在微软IE浏览器中的bug。不要在图片按钮中使用#default_value属性。图片按钮的值可从$form_state['clicked_button']['#value']中取出。

 

 

示例代码,来自(field_ui.admin.inc):

 

        $table[$name]['settings_edit'] = $base_button + array(

          '#type' => 'image_button',

          '#name' => $name . '_formatter_settings_edit',

          '#src' => 'misc/configure.png',

          '#attributes' => array('class' => array('field-formatter-settings-edit'), 'alt' => t('Edit')),

          '#op' => 'edit',

          // Do not check errors for the 'Edit' button, but make sure we get

          // the value of the 'formatter type' select.

          '#limit_validation_errors' => array(array('fields', $name, 'type')),

          '#prefix' => '<div class="field-formatter-settings-edit-wrapper">',

          '#suffix' => '</div>',

        );

 

常用属性: #access、 #after_build、 #ajax、 #attributes、 #button_type (默认为'submit')、 #disabled、 #element_validate、 #executes_submit_callback (默认为TRUE)、 #limit_validation_errors、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #return_value (默认为TRUE)、 #src、 #submit、 #states、 #suffix、 #theme、 #theme_wrappers、 #tree、 #type、 #validate、 #value、 #weight

 


Drupal版本:

27 item(条目)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

     条目元素的格式与其它输入表单元素相同,但有一点不同,那就是它是只读的,没有输入框。由于这个元素是只读的,所有#required属性基本没有用处,除非你为了好看,想在#title旁边添加一个表示必填的红色图标。

 

示例代码,来自(user.admin.inc):

 

$form['permission'][$perm] = array(

'#type' => 'item',

'#markup' => $perm_item['title'],

'#description' => theme('user_permission_description', array('permission_item' => $perm_item, 'hide' => $hide_descriptions)),

);       

 

常用属性: #access、 #after_build、 #description、 #element_validate、 #markup、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #required、 #states、 #suffix、 #theme、 #theme_wrappers、 #title、 #title_display、 #tree、 #type、 #weight


Drupal版本:

28 machine_name(机读名字)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    创建一个单行的文本字段,用来存储唯一的机读名字。对这个字段会进行验证,以确保它是唯一的,并且不包含非法字符。所有的非法字符将会通过JavaScript替换为有效字符。注意这个元素对中文的支持不是很友好。

 

    它还包含一个非标准的元素属性#machine_name,它是一个关联数组,可包含以下键existssourcelabelreplace_patternreplace。exists是一个回调函数,用来检查机读名字是否存在;source指的是表单元素的#array_parents包含的用户可读名字,可用作机读名字的源,默认为array('name')label,机读名字的标签文本,默认为“机读名字”;replace_pattern,正则表达式,用来匹配机读名字中不允许的字符,默认为'[^a-z0-9_]+'replace,在机读名字中,用来替换非法字符的字符,默认为'_'

 

示例代码,来自(content_type.inc):

 

$form['type'] = array(

'#type' => 'machine_name',

'#default_value' => $type->type,

'#maxlength' => 32,

'#disabled' => $type->locked,

'#machine_name' => array(

'exists' => 'node_type_load',

),

'#description' => t('A unique machine-readable name for this content type. It must only contain lowercase letters, numbers, and underscores. This name will be used for constructing the URL of the %node-add page, in which underscores will be converted into hyphens.', array(

'%node-add' => t('Add new content'),

)),

);

 

常用属性: #access、 #after_build、 #ajax、 #attributes、 #autocomplete_path (默认为FALSE)、 #default_value、 #description (默认为 '一个唯一的机读名字。只能包含小写的字母,数字和下划线。')、 #disabled、 #element_validate、 #field_prefix、 #field_suffix、 #maxlength (默认为64)、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #required (默认为TRUE)、 #size (默认为60)、 #states、 #suffix、 #theme (默认为'textfield')、 #theme_wrappers (默认为'form_element')、 #title (默认为'机读名字')、 #title_display #tree、 #type、 #weight

 


Drupal版本:

29 managed_file(受管理的文件)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    提供一个完整的ajax控件,用来上传文件并将其保存到{file_managed}表中。它包含一组表单元素,有两个'#submit',一个用来上传,一个用于删除;一个'#file' 元素;多个'#hidden''#markup'元素,用于处理进度条和显示已上传的文件。

 

    注意:新上传的文件,如果默认状态为0,那么它们会当作临时文件,每隔6个小时被清空一次。如果你的模块用到这个元素了,那么你需要自己负责修改$file对象的状态,将其改为FILE_STATUS_PERMANENT,并保存到数据库中。下面是参考代码:

 

//通过file.fid加载文件。
$file = file_load($form_state['values']['my_file_field']);

//将状态修改为持久化。
$file->status = FILE_STATUS_PERMANENT;

//保存。
file_save($file);

 

    如果点击了删除按钮,将会把该字段的值设置为0,你的模块还需要使用file_delete()将文件从files表和文件系统中实际的删除。

 

    它包含多个非标准的表单元素属性:#progress_indicator,可选值有'none''bar''throbber',默认为'throbber'#progress_message,文件正被上传时的进度信息,默认为NULL#upload_validators,一组回调函数,用来验证上传的文件;#upload_location ,上传文件应被存储的位置,例如'public://files/my_files'

 

示例代码,来自(image.field.inc):

 

$form['default_image'] = array(

    '#title' => t('Default image'),

    '#type' => 'managed_file',

    '#description' => t('If no image is uploaded, this image will be shown on display.'),

    '#default_value' => $field['settings']['default_image'],

    '#upload_location' => 'public://default_images/',

  );

 

常用属性: #access、 #after_build、 #array_parents、 #attached、 #attributes、 #description、 #disabled、 #element_validate、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #states、 #suffix、 #theme、 #theme_wrappers、 #title、 #tree、 #weight


Drupal版本:

30 markup(标识文本)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    它用来在表单中引入一段文本或者HTML如果一个元素没有设置属性#type,那么它就默认为标识文本元素。。

 

    注意:如果你把文本输出在一个可伸缩的字段集的内部,那么要使用<div>或者<p>标签对其进行包装,这样当字段集被折叠起来时,你的文本也被折叠在了里面;否则你的文本将显示在字段集的外面。

 

示例代码,来自(user.pages.inc):

 

$form['help'] = array('#markup' => '<p>' . t('This login can be used only once.') . '</p>');

 

常用属性: #access、 #after_build、 #element_validate、 #markup、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #states、 #suffix、 #theme、 #theme_wrappers、 #tree、 #type、 #weight

 

 

password(密码)

    生成一个单行文本字段,在这里用户的输入不直接显示出来。

 

示例代码,来自(user.module):

 

$form['account']['current_pass'] = array(

'#type' => 'password',

'#title' => t('Current password'),

'#size' => 25,

'#access' => !empty($protected_values),

'#description' => $current_pass_description,

'#weight' => -5,

);

 

常用属性:  #access、 #after_build、 #ajax、 #attributes、 #description、 #disabled、 #element_validate、 #field_prefix、 #field_suffix、 #maxlength (默认为128)、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #required、 #size (默认为60)、 #states、 #suffix、 #theme、 #theme_wrappers、 #title、 #title_display、 #tree、 #type、 #weight

 

 


Drupal版本:

31 password(密码)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    生成一个单行文本字段,在这里用户的输入不直接显示出来。

 

示例代码,来自(user.module):

 

$form['account']['current_pass'] = array(

'#type' => 'password',

'#title' => t('Current password'),

'#size' => 25,

'#access' => !empty($protected_values),

'#description' => $current_pass_description,

'#weight' => -5,

);

 

常用属性:  #access、 #after_build、 #ajax、 #attributes、 #description、 #disabled、 #element_validate、 #field_prefix、 #field_suffix、 #maxlength (默认为128)、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #required、 #size (默认为60)、 #states、 #suffix、 #theme、 #theme_wrappers、 #title、 #title_display、 #tree、 #type、 #weight


Drupal版本:

32 password_confirm(带确认的密码)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    该元素创建两个HTML密码字段,并附加一个验证器来检查两个密码是否匹配。

 

示例代码,来自(user.module):

 

$form['account']['pass'] = array(

'#type' => 'password_confirm',

'#size' => 25,

'#description' => t('Provide a password for the new account in both fields.'),

'#required' => TRUE,

); 

 

常用属性: #access、 #after_build、 #description、 #disabled、 #element_validate、 #field_prefix、 #field_suffix、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #required、 #size (默认为60)、 #states、 #suffix、 #theme、 #theme_wrappers、 #title、 #title_display、 #tree、 #type、 #weight


Drupal版本:

33 Radio(单选按钮)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    生成一个单选按钮。

 

示例代码,来自(user.pages.inc):

$form[$name] = array(

'#type' => 'radio',

'#title' => $method['title'],

'#description' => (isset($method['description']) ? $method['description'] : NULL),

'#return_value' => $name,

'#default_value' => $default_method,

'#parents' => array('user_cancel_method'),

);

 

常用属性: #access、 #after_build、 #ajax、 #attributes、 #default_value、 #description、 #disabled、 #element_validate、 #field_prefix、 #field_suffix、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #required、 #return_value、 #states、 #suffix、 #theme、 #theme_wrappers、 #title、 #title_display (默认为after)、 #tree、 #type、 #weight


Drupal版本:

34 radios(单选按钮)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    生成一组单选按钮。

 

示例代码,来自(user.module):

 

$form['account']['status'] = array(

'#type' => 'radios',

'#title' => t('Status'),

'#default_value' => $status,

'#options' => array(t('Blocked'), t('Active')),

'#access' => $admin,

);

 

常用属性: #access、 #after_build、 #ajax、 #attributes、 #default_value、 #description、 #disabled、 #element_validate、 #options、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #required、 #states、 #suffix、 #theme、 #theme_wrappers、 #title、 #title_display、 #tree、 #type、 #weight


Drupal版本:

35 select(下拉选择框)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    生成一个下拉选择框。

 

示例代码,来自(node.module):

$form['node_recent_block_count'] = array(

'#type' => 'select',

'#title' => t('Number of recent content items to display'),

'#default_value' => variable_get('node_recent_block_count', 10),

'#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 30)),

);

 

常用属性: #access、 #after_build、 #ajax、 #attributes、 #default_value、 #description、 #disabled、 #element_validate、 #empty_option、 #empty_value、 #field_prefix、 #field_suffix、 #multiple、 #options、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #required、 #size、 #states、 #suffix、 #theme、 #theme_wrappers、 #title、 #title_display、 #tree、 #type、 #weight


Drupal版本:

36 submit(提交按钮)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    生成一个表单提交按钮。按钮内部显示的单词默认为“提交”,但是可以使用属性#value来修改它。

 

示例代码,来自(node.module):

 

$form['advanced']['submit'] = array(

'#type' => 'submit',

'#value' => t('Advanced search'),

'#prefix' => '<div class="action">',

'#suffix' => '</div>',

'#weight' => 100,

);

 

常用属性: #access、 #after_build、 #ajax、 #attributes、 #button_type (默认为'submit')、 #disabled、 #element_validate、 #executes_submit_callback (默认为TRUE)、 #limit_validation_errors、 #name (默认为 'op')、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #submit、 #states、 #suffix、 #theme、 #theme_wrappers、 #tree、 #type、 #validate、 #value、 #weight


Drupal版本:

37 Tableselect(表选择)


    生成一个表格形式的单选按钮或者复选框,#headers属性用来设置列,#options属性用来设置行。#multiple属性用来决定是用单选按钮还是复选框,如果#js_select属性设置为TRUE,那么还会添加一个基于JavaScript的全选按钮。

    admin/content页面的节点内容列表,就用到了这个元素,如图所示:

图片1.png 

 

示例代码,来自(node.admin.inc):

 

$form['nodes'] = array(

'#type' => 'tableselect',

'#header' => $header,

'#options' => $options,

'#empty' => t('No content available.'),

);

 

 

常用属性: #access、 #after_build、 #ajax、 #attributes、 #default_value、 #element_validate、 #empty、 #header、 #js_select、 #multiple、 #options、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #states、 #suffix、 #theme、 #theme_wrappers、 #tree、 #type、 #weight


Drupal版本:

38 text_format(文本格式)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    文本域启用了文本格式的版本。它还包含两个非标准属性:

· #format: 要应用的格式。如果你想使用默认格式,那么可将这个属性设置为NULL,剩下的工作便会交给过滤器系统。

· #base_type (optional): 默认为'textarea'。也可以将文本格式选择器附加在其它表单元素类型上,比如文本字段。

 

示例代码,来自(user.module):

 

$form['signature_settings']['signature'] = array(

'#type' => 'text_format',

'#title' => t('Signature'),

'#default_value' => isset($account->signature) ? $account->signature : '',

'#description' => t('Your signature will be publicly displayed at the end of your comments.'),

'#format' => isset($account->signature_format) ? $account->signature_format : NULL,

);

 

常用属性: #access、 #after_build、 #ajax、 #attributes、 #cols (默认为 60)、 #default_value、 #description、 #disabled、 #element_validate、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #required、 #resizable (默认为 TRUE)、 #rows (默认为 5)、 #states、 #suffix、 #theme、 #theme_wrappers、 #title、 #title_display、 #tree、#type、 #weight


Drupal版本:

39 textarea(文本域)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    生成一个多行的文本字段。

 

示例代码,来自(user.admin.inc):

 

$form['email_admin_created']['user_mail_register_admin_created_body'] = array(

'#type' => 'textarea',

'#title' => t('Body'),

'#default_value' => _user_mail_text('register_admin_created_body', NULL, array(), FALSE),

'#rows' => 15,

);

 

常用属性: #access、 #after_build、 #ajax、 #attributes、 #cols (默认为60)、 #default_value、 #description、 #disabled、 #element_validate、 #field_prefix、 #field_suffix、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #required、 #resizable (默认为TRUE)、 #rows (默认为5)、 #states、 #suffix、 #theme、 #theme_wrappers、 #title、 #title_display、 #tree、 #type、 #weight 

    如果通过设置#resizable为TRUE,启用了动态的文本域调整器,那么属性#cols设置将不起作用。


Drupal版本:

40 textfield(文本字段)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    生成一个单行文本字段。

 

示例代码,来自(user.admin.inc):

$form['anonymous_settings']['anonymous'] = array(

'#type' => 'textfield',

'#title' => t('Name'),

'#default_value' => variable_get('anonymous', t('Anonymous')),

'#description' => t('The name used to indicate anonymous users.'),

'#required' => TRUE,

);

 

常用属性: #access、 #after_build、 #ajax、 #attributes、 #autocomplete_path (默认为FALSE)、 #default_value、 #description、 #disabled、 #element_validate、 #field_prefix、 #field_suffix、 #maxlength (默认为 128)、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #required、 #size (默认为60)、 #states、 #suffix、 #text_format、 #theme、 #theme_wrappers、 #title、 #title_display、 #tree、 #type、 #weight


Drupal版本:

41 value(值)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    值元素用在drupal表单内部,它不会显示出来。

 

示例代码,来自(user.module):

$form['picture']['picture'] = array(

'#type' => 'value',

'#value' => isset($account->picture) ? $account->picture : NULL,

);

    不要混淆了'#type' => 'value' '#value' => ’’。前者声明了正被描述的元素的类型,而后者声明了该元素的值。

 

常用属性: #type #value

 

 


Drupal版本:

42 vertical_tabs(垂直标签)

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    把所有的子字段集显示为垂直的标签。如图所示:

 

QQ截图20150624113023.png 

 

示例代码,来自(block.admin.inc):

$form['visibility'] = array(

'#type' => 'vertical_tabs',

'#attached' => array(

'js' => array(drupal_get_path('module', 'block') . '/block.js'),

),

);

常用属性: #access、 #after_build、 #default_tab、 #element_validate、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #states、 #suffix、 #theme、 #theme_wrappers、 #tree、 #type、 #weight

 

weight(重量)

    生成一个用来声明重量的下拉选择框。

 

示例代码,来自(taxonomy.admin.inc):

$form[$key]['weight'] = array(

'#type' => 'weight',

'#delta' => $delta,

'#title_display' => 'invisible',

'#title' => t('Weight for added term'),

'#default_value' => $term->weight,

);

 

常用属性: #access、 #after_build、 #attributes、 #default_value、 #delta (默认为10)、 #description、 #disabled、 #element_validate、 #parents、 #post_render、 #prefix、 #pre_render、 #process、 #required、 #states、 #suffix、 #theme、 #theme_wrappers、 #title、 #title_display、 #tree、 #type、 #weight


Drupal版本:

43 呈现API

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    如果你认真读过systemk_element_info函数中的代码,你会发现这里还定义了一个元素page。它是一个表单元素么?当然不是。在Drupal中,表单的这种数组形式的结构,经过不断的实践,发现它给我们带来了多个方面的便利性,因此在Drupal7中,这种概念又作了进一步的扩充,把它抽象成为了“呈现数组”(Render Array)。所有的表单、表单元素都属于“呈现数组”。但不是每一个“呈现数组”都是表单。也就是说,“呈现数组”这个概念范畴更广一点。

    在Drupal7中,对于各种创建的内容,基本上都采用“呈现数组”这种方式。在本章的第一个实例代码中,页面回调函数中,我们就采用了“呈现数组”。使用这种方式的好处时,以page为例,我们可以方便的修改其它模块创建的页面内容,就像我们修改其它模块创建的表单一样方便,所不同的是这里使用hook_page_alter钩子函数。例如:

function mymodule_page_alter(&$page) {  

  // 把搜索表单放在页脚.  

  $page['footer']['search_form'] = $page['sidebar_first']['search_form'];  

  unset($page['sidebar_first']['search_form']);    

  //删除"powered by Drupal"区块 

  unset($page['footer']['system_powered-by']);

}


Drupal版本:

2创建相关文件

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    我们不妨把模块的名字命名为contactus,在sites/all/modules/custom目录下创建一个contactus目录,接着创建三个文件,contactus.infocontactus.modulecontactus.pages.inc。我们向info文件添加以下内容:

 

name = 联系我们

description = 联系我们页面

core = 7.x

 

    接着,我们向contactus.module中添加hook_menu的实现:

<?php

 

/**

 * @file

 * 方便用户联系我们.

 */

 

/**

 * 实现钩子hook_menu().

 */

function contactus_menu() {

  $items = array();

  //联系我们菜单项

  $items['contactus'] = array(

    'tilte' => '联系我们',

    'page callback' => 'contactus_page',

    'type'     => MENU_CALLBACK,

    'access callback' =>TRUE,

    'file' => 'contactus.pages.inc',

  );

  //确认页面菜单项

  $items['contactus/confirm'] = array(

    'page callback' => 'contactus_confirm_page',

    'type'     => MENU_CALLBACK,

    'access callback' =>TRUE,

'file' => 'contactus.pages.inc',

  );

  //致谢页面菜单项

  $items['contactus/thanks'] = array(

    'page callback' => 'contactus_thanks_page',

    'type'     => MENU_CALLBACK,

    'access callback' =>TRUE,

'file' => 'contactus.pages.inc',

  );

  return $items;

}


Drupal版本:

3“联系我们”页面

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

   我们创建了三个菜单项,分别对应于联系我们、确认页面、致谢页面。接下来,我们首先为联系我们页面添加回调函数,向contactus.pages.inc中添加以下内容:

 

<?php

 

/**

 * @file

 * 各种页面的回调函数.

 */

 

/**

 * “联系我们”页面的回调函数

 */

function contactus_page(){

  //我们为这个页面设置标题

drupal_set_title('联系我们');


$render_array = array(

  '#markup' => '', 

);


//该页面的正文为一个表单,注意对于表单,这里需要使用drupal_render呈现一下。

$render_array['#markup'] .= drupal_render(drupal_get_form('contactus_form'));


//Drupal7的页面回调,返回的应该是一个数组

return $render_array;

}

 

Drupal需要对表单进行唯一的标识,这样当一个页面包含多个表单时,它就可以判定被提交的是哪一个表单,并且可以将表单与处理该表单的函数关联起来。为了唯一的标识表单,我们为每个表单分配了一个表单ID。在drupal_get_form()的调用中,所用的就是表单ID,如下所示:

drupal_get_form('contactus_form');

 

对于大多数的表单,其ID本身就是Drupal的一个函数名,因此符合Drupal中函数名的命名规则:“模块名字”+“表单描述”。例如,用户模块中的用户登录表单,它的ID为user_login,就符合这样的规则。

 

在表单的默认验证、提交、主题函数中,都用到了表单ID。另外,Drupal中表单ID和该表单最终生成的HTML版<form>标签中的ID属性还存在对应关系,我们很容易通过HTML版的ID来得到表单ID,对于我们的这个例子,我们可以使用firebug来查看表单的html,对于我们的这个表单,对应的html为:

<form id="contactus-form" accept-charset="UTF-8" method="post" action="/thinkindrupal/contactus">

 

这里我们看到,id="contactus-form",这是HTML版的ID,我们将连字符替换为下划线,就得到了表单ID,“contactus_form”。此外,在表单的HTML内部,我们还可以找到一个名为form_id的隐藏域,这里也存储了表单ID。通过这两个地方,我们可以反向的获取表单的ID,如果我们想使用form_alter修改一个已有表单时,这会非常有用。

 

    在我们的这个模块中,我们把表单命名为了contactus_form,它的描述性并不好,不过我们的这个模块中只有这么一个表单,所以这样命名也不会引起歧义,对于中文的开发者来说,如果找不到合适的英文单词,我们可以使用自己的拼音,然后加上注释,这样更可行一点,没有必要为了一个地道的英文名字而费上半天的功夫。

 

    现在就让我们看看contactus_form表单,接下来添加该表单的构建函数:

 

/**

 * “联系我们”表单的构建函数

 */

function contactus_form(){

  //添加我们自己的CSS,用来控制表单的样式

drupal_add_css(drupal_get_path('module', 'contactus').'/contactus.css');


  //提示信息,默认为markup类型。

$form['tips'] = array(

'#prefix' =>'<div id="tips">',

'#markup' => t('<span class="form-required">*</span> 号为必填项。'),

'#suffix' =>'</div>',

);


//表单元素“姓名”

$form['name'] = array(

  //表单元素的#title属性,对应于实际输出中的label

'#title' => t('姓名'),

//表单元素的类型,这里为textfield

'#type' => 'textfield',

//这个表单元素是必填的

'#required' => TRUE,

//表单元素的默认值,这里使用了三位运算符和isset进行判定

'#default_value' => isset($_SESSION['contactus_form']['name']) ? $_SESSION['contactus_form']['name'] : "",

//表单元素的描述,

'#description' => t('例如:周星驰'),

);


  //表单元素“单位名称”

$form['company_name'] = array(

'#title' => t('单位名称'),

'#type' => 'textfield',

'#required' => TRUE,

'#default_value' => isset($_SESSION['contactus_form']['company_name']) ? $_SESSION['contactus_form']['company_name'] : "",

'#description' => t('例如:北京无名信息技术公司'),

);


  //表单元素“电子邮件”

$form['mail'] = array(

'#title' => t('电子邮件'),

'#type' => 'textfield',

'#required' => TRUE,

'#default_value' => isset($_SESSION['contactus_form']['mail']) ? $_SESSION['contactus_form']['mail'] : "",

'#description' => t('例如:info@example.com'),

);


//表单元素“电话号码”

$form['phone'] = array(

'#title' => t('电话号码'),

'#type' => 'textfield',

'#required' => TRUE,

'#default_value' => isset($_SESSION['contactus_form']['phone']) ? $_SESSION['contactus_form']['phone'] : "",

'#description' => t('例如:010-88888888'),

);


  //表单元素“邮件正文”

$form['contact'] = array(

'#title' => t('邮件正文'),

//表单元素的类型,这里为textarea

'#type' => 'textarea',

'#required' => TRUE,

'#default_value' => isset($_SESSION['contactus_form']['contact']) ? $_SESSION['contactus_form']['contact'] : "",

);


//访问来源的可选项

$visit_options = array(

'baidu' =>t('百度'),

'google' =>t('谷歌'),

'sohu' =>t('搜狐'),

'other' =>t('其它'),

);

//表单元素“访问来源”

$form['visit'] = array(

'#title' => t('访问来源'),

//表单元素的类型,这里为radios

'#type' => 'radios',

//单选按钮的可选项。

'#options' => $visit_options,

'#default_value' => isset($_SESSION['contactus_form']['visit']) ? $_SESSION['contactus_form']['visit'] : "",

//为了便于控制radios的外观,我们使用#prefix#suffix为其添加了一个带有IDdiv

'#prefix' => '<div id="visit-radios">',

'#suffix' => '</div>',

);


//表单元素“其它”,它依赖于表单元素“访问来源”

$form['other'] = array(

'#title' => t('其它'),

'#type' => 'textfield',

'#default_value' =>isset($_SESSION['contactus_form']['other'])?$_SESSION['contactus_form']['other']:"",

//这里的意思是,当表单元素“访问来源”的值为“other”时,这个表单元素才显示出来

  '#states' => array(

  'visible' => array(

  ':input[name="visit"]' => array('value' => 'other'),

),

),

); 

/*

//表单元素“确认”提交按钮

  $form['submit'] = array(

    '#type' => 'submit',

    '#value' => t('确认'),

  );

  */


  //表单元素“确认”提交按钮,这里采用了图片的形式

  $form['image_submit'] = array(

  //表单元素的类型,这里为image_button

    '#type' => 'image_button',


//图片按钮特有的#src属性,

    '#src' => drupal_get_path('module','contactus').'/images/button1-1.jpg',

    '#value' => 'image_sub',


//为表单元素添加两个属性,onmouseoutonmouseover,为了在鼠标移到按钮上时,显示不同的图片效果

    '#attributes' =>array(

      'onmouseout' => "this.src='".base_path().drupal_get_path('module','contactus')."/images/button1-1.jpg'",

      'onmouseover' => "this.src='".base_path().drupal_get_path('module','contactus')."/images/button1-2.jpg'",

    ),


    //为了便于控制image_button的外观,我们使用#prefix#suffix

    //为其添加了一个带有IDdiv

    '#prefix' => '<div id="image-submit">',

    '#suffix' => '</div>',

  );

 

  return $form;

}

 

    我们在这个表单中,用到了markuptextfieldradiosimage_button等常用的表单元素,关于这几个表单元素和其它表单元素的详细介绍,可参看后面的表单元素一节。另外,上面的代码中,有不少的注释,我们在这里也就不再过多地重复了。我们讲解一下,这里面的重点。

    我们启用这个模块,访问页面contactus。此时我们就可以看到我们创建的表单的实际效果了。如图所示:

图片1.png

Drupal版本:

4控制表单的外观

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    如果你细心一点的话,你就会发现,我们的这个表单与默认的表单,在样式上面有所不同。是的,我们单独的为这个表单定义了自己样式。注意代码中的:

    drupal_add_css(drupal_get_path('module', 'contactus').'/contactus.css');

    这一代码,表示向这个表单添加CSS文件contactus.css,当加载这个表单时,就会加载这个CSS文件。通过在CSS文件中定义样式规则,我们便可以控制80%的表单外观了。首先,通过CSS代码,我们实现了表单元素的label标签与input元素左右显示,默认情况下,是上下显示的。我们来看看对应的CSS代码:

#contactus-form .form-item{

  position:relative;

  left:10px;

  top:5px;

  margin-bottom:5px;

  overflow:hidden;

}

 

#contactus-form .form-item label{

float: left;

  position:relative;

width: 180px;

font-size:12px;

}

 

#contactus-form .form-item input{

width: 250px;

}

 

    在这里,我们固定了labelinput的宽度,并使label向左浮动,从而实现了左右排列的效果。contactus.css文件中的更多的CSS代码,我们就不再这里列出了,大家可以登录我的网站http://zhupou.cn,来下载所有的实例模块的完整代码。

 

    为了控制具体表单元素的外观,我么还用到了属性#prefix和#suffix。使用这两个属性,我们可以方便的为表单元素添加更具有表述性的ID:

        '#prefix' => '<div id="visit-radios">',

'#suffix' => '</div>',

 

    我们看一下,表单的最下面,我们这里使用了图片按钮,来替代默认的表单提交按钮元素。我这里的图片按钮看起来有点单薄,这是因为作者的美工效果实在不怎么样。如果换一个好一点的美工,加上上面所给的代码,一定能够实现非常酷的效果。另外注意的是,鼠标移到图片按钮上时,会变色,我们看看前后对比:

前:                           后:      

                图片1.png 图片2.png

    这是因为我们作了两个图片,通过JS,在鼠标移进和移出时变换图片,来实现的这一效果。对应代码:

'#attributes' =>array(

      'onmouseout' => "this.src='".base_path().drupal_get_path('module','contactus')."/images/button1-1.jpg'",

      'onmouseover' => "this.src='".base_path().drupal_get_path('module','contactus')."/images/button1-2.jpg'",

    ),

    最后,让我们看一下访问来源这个表单元素,当我们选择“其它”的时候,表单显示出来一个文本输入框,允许我们输入一个具体的访问来源:

图片3.png 

    其实这里,我们做了两个表单元素,一个是visit、一个是other,后一个是一个文本输入框。对于表单元素other,我们为其使用了'#states'属性:

'#states' => array(

'visible' => array(

':input[name="visit"]' => array('value' => 'other'),

),

),

 

    这段代码的意思是说,当表单元素visit的值等于'other'时,显示这个表单元素。我们在这里没有使用任何JS,仅仅添加了'#states',就实现了这一效果。实际上,如果我们看一下,生成的html代码,我们就会发现表单元素other是存在的,只不过被隐藏了起来,当我们点击访问来源并选择“其它”时,该元素显示了出来。在这里,Drupal替我们做了很多工作。

    

    此外,为了控制表单的外观,我们还可以为表单添加主题函数,我们也可以使用#theme属性为任意一个表单元素添加主题函数。在默认情况下,contactus_form对应的主题函数应该为theme_contactus_form。当然,对于使用#theme属性添加的主题函数,可以根据需要按照Drupal函数的命名规则加以命名。关于主题的更多知识,我们可以参看主题系统一章。


Drupal版本:

5添加验证函数和提交函数

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    定义好表单以后,通常接下来的工作就是为表单添加验证函数,用来检查用户的输入,让我们在代码中添加验证函数对应的代码:

 

/**

 * contactus_form表单的验证函数

 */

function contactus_form_validate($form, &$form_state){

  //这里使用正则表达式,来验证邮箱的有效性,注意,这里的正则表达式,包围在"/...../"之间。

if(!preg_match("/^[\w\-\.]+@[\w\-\.]+(\.\w+)+$/", $form_state['values']['mail'])){

form_set_error('mail',t('您输入的电子邮件地址格式不正确'));

}

}

 

    在这里,对于用户的输入,我们只对电子邮件字段进行了检查。如果我们在电子邮件输入框中输入了非法的电子邮件,比如:

 

图片1.png 

 

那么当我们提交表单时,系统就会给出错误提示:

 

图片2.png 

    通过上面的例子,我们可以看出,form_set_error的第一个参数,对应于表单元素的ID,第二个参数为显示给用户的错误提示。

 

    在我们的这个默认的验证函数中,我们可以为所有的表单元素添加验证规则,因此在一般情况下,一个默认的验证函数就足够了。当然,我们可以使用#validate属性,为表单添加一组验证。我们在用户系统一章,讲到的用户登录表单,就用到了#validate属性。此外,对于单个的表单元素,我们也可以为其设置验证函数,这和表单的验证函数类似,不过需要使用表单元素的#element_validate属性。

 

如果我们在验证函数做了大量的处理,而你又想把结果保存下来,供后面的提交函数使用,此时有两种不同的实现方式。一是使用form_set_value(),二是使用$form_state。我们来看一下$form_state的这种方式,这种方式我用到过:

 

    由于$form_state在验证和提交函数中都是通过引用传递的,所以在验证函数中,可以将数值存储在这里,而在提交函数中,就可以使用它了。最好在$form_state中加上你模块的命名空间,这样有利于避免命名冲突。

 

    在实际的工作中,我就用到过这一点,在一个模块的验证函数中,我需要从远程获取一个图书的ID号,这个操作由于调用的是web service,所以比较耗费资源,而在提交处理函数中,我需要使用这个ID号。

 

//通过调用web service获取图书的唯一ID

$book_id = my_module_get_book_id_by_service($bsno);

 

//我们将在验证中获取的值保存起来,这样在提交函数中可以使用。

$form_state['my_module']['book_id'] = $book_id;

 

接着,在提交函数中,我这样访问该数据:

$book_id = $form_state['my_module']['book_id'];

 

 

    表单完全通过验证后,接下来我们需要对通过表单提交的数据进行处理。让我们为表单添加对应的提交处理函数。

 

/**

 * contactus_form表单的提交函数

 */

function contactus_form_submit($form, &$form_state){

  //把表单的值存放在会话中去,由于这里涉及到了两个不同的表单之间传值。

$_SESSION['contactus_form'] = $form_state['values'];

//表单重定向到确认页面

$form_state['redirect'] = 'contactus/confirm';

}

 

    在我们的这个处理函数,逻辑相当简单,我们仅仅把表单的提交值,保存到了会话信息中。然后将表单重定向到了确认页面。如果我们不修改$form_state['redirect']的话,表单提交后,仍然会回到当前表单所在的页面。另外,这里最好不要使用drupal_goto,尽量使用$form_state['redirect']的方式。如果存在多个提交处理函数的话,你使用了drupal_goto,那么接下来的提交处理函数可能就被跳过了。如果我们有多个提交处理函数,每个函数中都设置了$form_state['redirect'],那么最后起作用的应该是最后设置的那个。

 

    在两个表单页面之间传值,可以通过URL,也可以先将值保存到数据库中,然后再读取。我们这里用到的会话方式,本质上就是使用的后一种方式。刚学Drupal的时候,对这一点还不是很熟悉,总以为不应该向会话信息中保存太多信息,Java中就是这样,但是DrupalJava不同,这里的会话信息会保存在数据库中,而不是停留在内存中供下次调用。

 


Drupal版本:

6确认页面

 作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    接下来,让我们看一下确认页面的回调代码:

 

/**

 * “确认”页面的回调函数

 */

function contactus_confirm_page(){

  //我们为这个页面设置标题

drupal_set_title('联系我们');


//这里首先作了判断,如果会话中没有设置contactus_form,返回contactus

if(empty($_SESSION['contactus_form'])){

drupal_goto('contactus');

}else{

}

 

$render_array = array(

  '#markup' => '', 

);

//该页面的正文为一个表单,注意对于表单,这里需要使用drupal_render呈现一下。

$render_array['#markup'] .= drupal_render(drupal_get_form('contactus_confirm_form'));

//Drupal7的页面回调,返回的应该是一个数组

return $render_array;

}

 

    这段代码和前面的类似,只是在函数的前面作了一下检查,检查会话中没有设置contactus_form,如果没有,则返回到“联系我们”页面。如果设置了,则会呈现确认表单,我们来看一下确认表单的定义:

 

/**

 * “确认”表单的构建函数

 */

function contactus_confirm_form(){

  //添加我们自己的CSS,用来控制表单的样式

drupal_add_css(drupal_get_path('module', 'contactus').'/contactus.css');

//drupal_set_message(print_r($_SESSION['contactus_form']));

//表单元素“姓名”

$form['name'] = array(

'#title' => t('姓名'),

//表单元素的类型,这里为item

'#type' => 'item',

//'#default_value' => $_SESSION['contactus_form']['name'],

//表单元素的#markup,Drupal6下面,我用的是#value,7下面就无法工作,改为了#default_value,还是不行,

//最后改为#markup,才可以了

'#markup' => isset($_SESSION['contactus_form']['name'])?$_SESSION['contactus_form']['name']:"",

);


  //表单元素“单位名称”

$form['company_name'] = array(

'#title' => t('单位名称'),

'#type' => 'item',

//这是我在调试的时候,使用#value#default_value#description分别测试时的代码,这里保留了。

//'#value' => $_SESSION['contactus_form']['company_name'],

//'#value' => '123456',

'#markup' => isset($_SESSION['contactus_form']['company_name'])?$_SESSION['contactus_form']['company_name']:"",

//'#description' => '123456',

);


//表单元素“电子邮件”

$form['mail'] = array(

'#title' => t('电子邮件'),

'#type' => 'item',

'#markup' => isset($_SESSION['contactus_form']['mail'])?$_SESSION['contactus_form']['mail']:"",

);


//表单元素“电话号码”

$form['phone'] = array(

'#title' => t('电话号码'),

'#type' => 'item',

'#markup' => isset($_SESSION['contactus_form']['phone'])?$_SESSION['contactus_form']['phone']:"",

);


  //表单元素“邮件正文”

$form['contact'] = array(

'#title' => t('邮件正文'),

'#type' => 'item',

'#markup' => isset($_SESSION['contactus_form']['contact'])?$_SESSION['contactus_form']['contact']:"",

);


//表单元素“访问来源”

$form['visit'] = array(

'#title' => t('访问来源'),

'#type' => 'item',

'#markup' => isset($_SESSION['contactus_form']['visit'])?$_SESSION['contactus_form']['visit']:"",

);


//如果访问来源,我们选择了“其它”,此时使用other表单元素的值来替换$form['visit']['#markup']

if( isset($_SESSION['contactus_form']['visit']) && $_SESSION['contactus_form']['visit'] == 'other'){

$form['visit']['#markup'] = isset($_SESSION['contactus_form']['other'])?$_SESSION['contactus_form']['other']:"";

}

/*

//表单元素“返回”按钮

  $form['back'] = array(

    '#type' => 'submit',

    '#value' => t('返回'),

    '#submit' => array('contactus_confirm_form_back'),

  );

//表单元素“提交”按钮

  $form['submit'] = array(

    '#type' => 'submit',

    '#value' => t('提交'),

  );

  */


  //表单元素“返回”图片按钮

  $form['image_back'] = array(

    '#type' => 'image_button',

    '#src' => drupal_get_path('module','contactus').'/images/button2-1.jpg',

//使用这个按钮提交时,对应的提交处理函数为contactus_confirm_form_back

    '#submit' => array('contactus_confirm_form_back'),

    '#executes_submit_callback' => TRUE,

//为表单元素添加两个属性,onmouseoutonmouseover,为了在鼠标移到按钮上时,显示不同的图片效果

    '#attributes' =>array(

      'onmouseout' => "this.src='".base_path().drupal_get_path('module','contactus')."/images/button2-1.jpg'",

      'onmouseover' => "this.src='".base_path().drupal_get_path('module','contactus')."/images/button2-2.jpg'",

    ),

  );


  //表单元素“提交”图片按钮

  $form['image_submit'] = array(

    '#type' => 'image_button',

    '#src' => drupal_get_path('module','contactus').'/images/button3-1.jpg',

    '#executes_submit_callback' => TRUE,

//使用这个按钮提交时,对应的提交处理函数为contactus_confirm_form_submit

    '#submit' => array('contactus_confirm_form_submit'),

    '#attributes' =>array(

      'onmouseout' => "this.src='".base_path().drupal_get_path('module','contactus')."/images/button3-1.jpg'",

      'onmouseover' => "this.src='".base_path().drupal_get_path('module','contactus')."/images/button3-2.jpg'",

    ),

  );

  

  return $form;

}

    这个确认表单,其实比前面的第一表单还要简单一些,这里主要就用到了item元素。而图片按钮的用法则和前面一致。对于item元素,我最初使用'#value'属性,后来改为'#default_value'属性,但总是显示不出来值,最后才改用'#markup'。注释里面讲到了这一点,这里再强调一下,因为在Drupal6里面,我用的是'#value'属性,能够正常工作,在Drupal7下面却显示不出来,所以这里给以前熟悉Drupal6的开发者提个醒,避免出现同样的错误。

 

    对于这个确认页面,我们仍然为其添加了CSS文件,用来控制样式,当我们提交表单后,得到一个如下图所示的确认表单。


Drupal版本:

7联系我们

图片1.png当我们点击“返回”按钮,就会回到前面的表单页面,当我们点击“提交”按钮时,就会正式的提交填写的数据。

 

    这个表单,我们没有定义验证函数,因为这里没有输入框。对于“返回”按钮和“提交”按钮,我们分别为其使用'#submit'属性为其定义了对应的提交处理函数。我们接下来看看这两个处理函数:

 

/**

 * 返回按钮对应的提交函数

 */

function contactus_confirm_form_back($form, &$form_state){

  //简单的重定向到contactus页面

$form_state['redirect'] = 'contactus';

}

 

/**

 * 提交按钮对应的提交函数

 */

function contactus_confirm_form_submit($form, &$form_state){

$values = NULL;

//从会话中获取用户最初提交的值,并将$_SESSION['contactus_form']置为空。

if(empty($_SESSION['contactus_form'])){

drupal_goto('contact');

}else{

$values = $_SESSION['contactus_form'];

unset($_SESSION['contactus_form']);

}


//收件人地址,这里为作者的邮箱

$to = 'g089h515r806@gmail.com';

//用户填写的邮箱地址

$from = $values['mail'];


//发送邮件

drupal_mail('contactus', 'contact', $to, language_default(), $values, $from);


//简单的重定向到致谢页面

$form_state['redirect'] = 'contactus/thanks';

}

 

   “返回”按钮的提交处理函数contactus_confirm_form_back的逻辑非常简单,就是一个重定向。“提交”按钮的处理函数contactus_confirm_form_submit,则包含获取用户最初提交的数据、发送邮件、重定向页面等三个组成部分。这个提交处理函数,是我们整个模块中最复杂的一个了,很多实际的提交处理函数可能比这个还要复杂一点。

 


Drupal版本:

8邮件发送

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    这里我们用到了Drupal的邮件发送,drupal_mail是一个API函数,专门用来发送邮件的。我们这里做一下简单的介绍:

 

drupal_mail($module, $key, $to, $language, $params = array(), $from = NULL, $send = TRUE)

 

  $module表示hook_mail()所触发的模块的名字,也就是我们将调用{$module}_mail()

  $keyhook_mail中定义的键。

  $to表示收件人地址。

  $language表示合成邮件所用的语言对象,默认为当前语言。

  $params,表示用于构建电子邮件的可选参数

  $from,表示发件人地址。

  $send,表示直接发送邮件。

 

下面是我们的hook_mail的具体实现:

/**

 * 实现钩子hook_mail().

 */

function contactus_mail($key, &$message, $params){

$language = $message['language'];

  switch ($key) {

   case 'contact':


//邮件的标题

    $message['subject'] = '联系我们';


//邮件正文,这里面包含:姓名、单位名称、电子邮件、电话号码、邮件正文、访问来源

      $message['body'][] = '姓名:'.$params['name'];

      $message['body'][] = '单位名称:'.$params['company_name'];

      $message['body'][] = '电子邮件:'.$params['mail'];

      $message['body'][] = '电话号码:'.$params['phone'];

      $message['body'][] = '邮件正文:'.$params['contact'];


      //对于访问来源,如果visit的值我们选择了“其它”,那么此时我们取$params['other'],否则取$params['visit']

      $visit = "";

      if($params['visit'] == 'other'){

        $visit = $params['other'];

      }else{

        $visit = $params['visit'];

      }

      $message['body'][] = '访问来源:'. $visit;

  }

}

    这里$message['body']表示邮件的正文部分,这里把用户提交的信息全部加了进来。这里的$message是一个数组,除了上面用到的subjectbody以外,还可以用的键有idtofromheaders$key表示邮件的标识。$params则是传递过来用于构建邮件的关联数组。


Drupal版本:

9致谢”页面

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    最后让我们看一下,“致谢”页面的回调代码,这里仅仅用来显示一段感谢语:

/**

 * “致谢”页面的回调函数

 */

function contactus_thanks_page(){

  //我们为这个页面设置标题

drupal_set_title('联系我们');

$render_array = array(

  '#markup' => '', 

);

//Drupal7的页面回调,返回的应该是一个数组

$render_array['#markup'] .= '<div id="contactus-thanks">';

$render_array['#markup'] .= t('感谢您的来信,我们会在第一时间给您回复。');

$render_array['#markup'] .= '</div>';


return $render_array;

}

 

    通过这个例子,希望大家能够熟悉表单的定义,验证函数、提交函数的编写,以及了解两部表单的实现方法。

 


Drupal版本: