一个field通常是Drupal中的一个成分,用来存储一个值,记录用户登录或一个模块建立。字段的信息在数据库中校验、存储,可以从数据库中抽取并显示在网站上。字段的例子包括用户名、街道地址、电话号码、价格、一个或两个描述事件的段落、一个日期或其他任何你想象的信息片段。
在Drupal 7中,建立字段有了人人瞩目的改变——Field API加到了核心里。过去为了校验、存储、抽取、显示字段层信息而定义表、写代码是个乏味冗长的任务,现在处理这些通过一个Field API集合。许多字段层特性都加入了Drupal7的核心,来自Drupal6的CCK模块,CCK模块提供一个UI层接口来在Drupal先前版本建立字段。
在本章中,我将展示标准的建立到Drupal7核心的类CCK功能,怎样通过增加一个新的字段类型来扩展功能,任何一个站点管理员都可以讲这个新的字段类型附加到内容类型中,最后展示怎样在一个模块中用Field API通过几个不同的字段类型来建立一个新的内容类型。
创建内容类型
Drupal有个杀手级应用,就是可以创建自定义内容类型,自定义内容类型被定义为创建一个节点的框架,内容类型典型地包含一些字段,最少一个title字段、一个body字段和几个其它用来获取结构化信息的字段。例如一个event自定义内容类型,event有一些字段来获取、存储、显示信息,诸如事件名字、事件描述(body)、事件的日期和时间、事件的位置。让我们通过后台来建立一个新的event内容类型:Structure->Content Type点击Add content type连接。建立一个新的内容类型相对简单——输入内容类型要呈现的名称的值(此例中是Event)几个简短的描述,以及可选的覆盖Event节点标题的标签,此例中我们将标签从Title改到Event Title(见图8-1)。


为一个内容类型增加字段
两个为Event内容类型增加的字段是事件的定位和事件的日期/时间。我们从定位字段开始,在标签那输入Event Location,字段名称那里输入event_location,我们选择“text field”组件来作为这个字段的存储的数据类型(如图8-3)




创建一个自定义字段
Drupal7核心自带的几个通用字段类型能满足大多数不同的目的,你能用前面的字段类型为广大不同的目的去捕获、存储和显示值,但是,这有许多可能是标准的字段类型不是你需要的,这些可以使Field API起作用、激活自定义字段的建立,它用来在你的站点上建立任何的字段类型。 Table 8-1 . Standard Field Types in Drupal 7 Core ---------------------------------------------------------------------------------------------- field type usage ---------------------------------------------------------------------------------------------- Boolean 用于在复选框或单选框中收集真假值 Decimal 用于收集包含小数点的十进制数字 File 提供一个文件上传字段允许作者附加一个文件到你的 内容类型的一个实例 Image 提供一个图片上传字段允许作者附加一个图片到你的 内容类型的一个实例 Integer 提供一个文本字段使作者能在此输入一个整数 List 提供一个建立选择列表(弹出、或select值列表)的 List(numeric) 或复选框/单选框按钮使作者可以选择一个或多个预先 List(text) 提供的值 Long text 提供一个多行文本区使作者可以在此输入信息 Long text and summary Term 提供选择taxonomy term的能力 reference Text 一个简单的文本框 -------------------------------------------------------------------------------------------- 借助Field API,你有能力去构建一个自定义字段满足几乎任何类型的你能想到的数据输入。一个例子,我们将使用Drupal.org上的字段类型例子(http://api.drupal.org/api/drupal/developer--examples--color_example--color_example.module/7),它定义一个自定义字段,在字段类型设置字段时,将文本渲染成颜色。我们使用这个字段类型来捕获和显示事件参与者出席未来事件应该穿什么颜色。 第一步在sites/all/modules/custom中建立一个目录叫color_example,在此目录中建立新文件color_example.info,输入下面内容:name = Color Example description = "Create a custom field for inputting and displaying text in a colorful fashion" package = Pro Drupal Development core = 7.x files[] = color_example.module php = 5.2下一步建立其它文件名叫color_example.module,放入下面内容
/** * @file * An example field using the Field API * */保存文件,激活这个模块。我们已经准备好构造新的RGB字段类型细节了。 第一步调用hook_field_info(),它定义我们的新字段的基本属性,我们定义字段为color_example_rgb()并且分配一个标签、描述、默认组件和默认格式到新的字段类型。
/** * Implements hook_field_info(). * * Provides the description of the filed */ function color_example_field_info() { return array( 'color_example_rgb' => array( 'lable' => t('Example Color RGB'), 'description' => t('Demonstrates a field composed of an RGB color.'), 'default_widget' => 'color_example_3text', 'default_formatter' => 'color_example_simple_text', ), ); }下一步定义在此字段收集的数据怎样在Drupal数据库中存储,在D7和Field API之前,我们要在模块中自己定义表和图表来定义我们的内容类型和内容类型中用到的自定义字段,在D7中,利用Field API,这个任务由hook_field_schema()为我们处理,例如,我们存储一个用于在屏幕上渲染文本的描述HTMLhex颜色代码的7字符字段——例如使用#FF0000渲染红色文本。在此例中,我们建立一个单个的column存贮由站点管理员输入的RGB的值,假如他把这个字段分配给一个内容类型的话。
/** * Implements hook_field_schema(). */ function color_example_field_schema($field) { $columns = array( 'rgb' => array('type' => 'varchar', 'length' => 7, 'not null' => FALSE), ); $indexes => array( 'rgb' => array('rgb'), ); return array( 'columns' => $columns, 'indexes' => $indexes, ); }下一步是用hook_field_validate()校验用户的输入,我要告诉Drupal用户输入的值匹配一个典型HTML颜色代码模式,我将检视第一个字符是不是“#”并且下面跟随的是不是数字或一个在a-f之间的字符,如果输入的值不匹配这个模式,我将显示一个错误。
/** * Implements hook_field_validate(). * * Verifies that the RGB field as combined is valid * (6 hex digits with a # at the beginning). */ function color_example_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) { foreach ($items as $delta => $item) { if (!empty($item['rgb'])) { if (!preg_match('@^#[0-9a-f]{6}$@', $item['rgb'])) { $errors[$field['field_name']][$langcode][$delta][] = array( 'error' => 'color_exmple_invalid', 'message' => t('Color must be in the HTML format #abcdef.'), ); } } } }下一个函数定义什么构成了这个类型的空字段,在这种情况,我们使用PHP空函数返回TRUE或FALSE二者之一,这依赖于此字段是否为空。
/** * Implements hook_field_is_empty(). */ function color_example_field_is_empty($item, $field) { return empty($item['rgb']); }1.字段格式器是一个函数,定义字段的内容是怎样显示的,钩子hook_field_formatter_info()函数标识格式器的类型,在我们的例子中她用于显示文本和背景。
/** * Implements hook_field_formatter_info(). */ function color_example_filed_formatter_info() { return array( // this formatter just display the hex value in the color indicated. 'color_example_simple_text' => array( 'label' => t('Simple text-based formatter'), 'field types' => array('color_example_rgb'). ), // This formatter changes the background color of the content region. 'color_example_color_background' => array( 'lable' => t('Change the background of the output text'), 'field types' => array('color_example_rgb'), ), ); }下面我将为这两个格式器建立可渲染输出,两个格式器是这样定义的: 1. color_example_simple_text只输出标记指示颜色,此颜色是输入的并使用inline风格来设置文本颜色为此值。 2. color_example_color_background 作用相同但是改变div.region-content的背景颜色。
/** * Implements hook_field_formatter_view(). */ function color_example_filed_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { $element = array(); witch ($display['type']) { // This formatter simply output the field as text and with a color. case 'color_example_simple_text': foreach ($items as $delta => $item) { $element[$delta]['#markup'] = '<p style="color: ' . $item['rgb'] . '">' . t('The color for event is @code', array('@code' => $item['rgb'])) . '</p>'; } break; // This formatter adds css to the page changing the '.region-content' area's // background color. If there are many fields, the last one will win. case 'color_exmple_color_background': foreach ($items as $delta => $item) { drupal_add_css('div.region-content { background-color:' . $item['rgb'] . ';}', array('type' => 'inline')); $element[$delta]['#markup'] = '<p>' . t('The color for this event has been changed to @code', array('@code' => $item['rgb'])) . '</p>'; } break; } return $element; }下一个函数定义校验用户的输入数据
/** * Validate the individual fields and the convert them into a single HTML RGB * value as text. */ function color_example_text_validate($element, &$form_state) { $delta = $element['#delta']; $field = $form_state['field'][$element['#field_name']][$element['#language']]['field']; $field_name = $field['field_name']; if (isset($form_state['values'][$field_name][$element['#language']][$delta])) { $value = $form_state['values'][$field_name][$element['#language']][$delta]; foreach (array('r', 'g', 'b') as $colorfield) { $val = hexdec($values[$colorfield]); // If they left any empty, we'll set the value empty and quit. if (strlen($values[$colorfield) == 0) { form_set_value($element, array('rgb' => NULL), $form_state); return; } // If they gave us anything that's not hex reject it. if ((strlen($values[$colorfield]) != 2) || $val <0 || val > 255) { form_error($element[$colorfield], t('Saturation value must be a 2-digit hex value between 00 and ff.')); } } $value = sprintf('#%02s%02s%02s', $values['r'], $values['g'], &values['b']); form_set_value($element, array('rgb' => $value), $form_state); } }最后,我用hook_field_error()来在用户输入无效时显示错误信息。
/** * Implements hook_field_error(). */ function color_example_field_widget_error($element, $error, $form, $form_state) { switch ($error['error']) { case 'color_example_invalid': form_error($element, $error['message']); break; } }下一个文件是Javascript文件,提供一个farbtastic颜色选择器,在这个目录中建立color_example.js,包含下列代码:
/** * @file * Javascript for Color Example. */ /** * Provide a farbtastic colorpicker for the fancier widget. */ (function($) { Drupal.behaviors.color_example_colorpicker = { attach: function(context) { $(".edit-field-example-colorpicker").live("focus", function(event) { var edit_field = this; var picker = $(this).closest('tr').find(".field-example-colorpicker"); // Hide all color picker except this one. $(".field-example-colorpicker").hide(); $(picker).show(); $.farbtastic(picker, function(color) { edit_field.value = color; }).setColor(edit_field.value); }); } } })(jQuery);最后一个文件是本模块必须的CSS文件,建立一个叫color_example.css的新文件包含下面CSS代码:
/** * @file * CSS for Color Example */ div.form-item table .form-type-textfield, div.form-item table .form-type-textfield * { display: inline-block; }保存完这个模块之后,字段已经准备好去加到一个内容类型里啦,通过Structure->Content Types来为Event增加color字段,点击Event内容类型的Manage Fields选项卡将展示如图8-8情形:




程序化增加字段
Field API能被用来程序化地增加字段到一个内容类型或节点类型,下面的例子演示使用Field API来增加一个新字段到一个由模块创建的内容类型。Job Post模块创建内容类型,它扩展自常规节点(title和body),增加了一个新的字段,用来存储和显示发布工作信息的公司的名称,增加字段的程序发生在模块的.install文件的hook_install()和hook_update()函数中。 在hook_install()函数中第一步是增加body字段到我们的新的Job Post内容类型,默认情况下,通过模块建立的内容类型只包含title字段,你必须隐含的增加body字段。node.module定义了一个名叫node_add_body_field()的函数,用它增加标准的body字段到我们的Job Post内容类型。下一步我将遍历的是增加所有要增加到Job Post内容类型中的字段定义,本例子的情况是,只有一个叫job_post_companies的字段需要增加,如果我想增加多个字段,我能通过在_job_post_installed_fields()函数中增加字段定义来完成。定义新字段简单的事情,给定一个名字、一个标签、和一个字段类型。建立字段的最后的步骤是使用field_create_instance()函数实例化字段,这个函数做完所有的幕后工作,诸如在数据库中创建存储机制来获取用户输入的值,当你看见这个情景,这里不需要再数据库中定义表——Field API为你做了所有的工作。在你安装模块后,这个字段就在你的节点编辑表单里啦,已准备好为你的模块使用。另外关于怎样使用和主题化自定义字段的输出请看第7章。<?php /** * @file * Install file for Job Post module */ /** * Implement hook_install(). * * - Add the body field. * - Configure the body field. * - Create the company name field. */ function job_post_install() { node_types_rebuild(); $types = node_type_types(); node_add_body_field($types['job_post']); // Load the instance definition for our content type's body. $body_instance = field_info_instance('node', 'body', 'job_post'); // Add our job_post_list view mode to the body instance display by. $body_instance['type'] = 'text_summary_or_trimmed'; // Save our changes to the body field instance. field_update_instance($body_instance); // Create all the fields we are adding to our content type. foreach (_job_post_installed_field() as $field) { field_create_field($field); } // Create all the instances for our fields. foreach (_job_post_installed_instances() as $instance) { $instance['entity_type'] = 'node'; $instance['bundle'] = 'job_post'; field_create_instance($instance); } } /** * Return a structured array defining the fields create by this content type. */ function _job_post_installed_fields() { $t = get_t(); return array( 'job_post_company' => array( 'field_name' => 'job_post_company', 'label' => $t('Company posting the job listing'), 'type' => 'text', ), ); } /** * Return a structtured array defining the instances for this content type. */ function _job_post_installed_instances() { $t = get_t(); return array( 'job_post_company' => array( 'field_name' => 'job_post_company', 'type' => 'text', 'label' => $t('Company posting the job listing'), 'widget' => array( 'type' => 'text_textfield', ), 'display' => array( 'job_post_list' => array( 'label' => $t('Company posting the job listing'), 'type' => text, ), ), ), ); } /** * Implements hook_uninstall(). */ function job_post_uninstall() { // Gather all the example content that might have been created while this // module was enable. $sql = 'SELECT nid FROM {node} n WHERE n.type = :type'; $result = db_query($sql, array(':type' => 'job_post')); $nids = array(); foreach ($result as $row) { $nids[] = $row->nid; } // Delete all the nodes at once node_delete_multiple($nids); // Loop over each of the fields defined by this module and delete // all instances of the field, their data, and the field itself. foreach (array_keys(_job_post_installed_fields()) as $field) { field_delete_field($field); } // Loop over any remaining field instances attached to the job_post // content type (such as body field) and delete them individually. $instances = field_info_instances('node', 'job_post'); foreach ($instances as $instance_name => $instance) { field_delete_instance($instance); } // Delete our content type. node_type_delete('job_post'); // Purge all field information field_purge_batch(1000); }