第9章 Field API

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

 

    对于熟悉Drupal6的用户来说,CCK应该是必选的第三方模块,使用这个模块,可以方便的扩展内容类型的字段信息。Drupal7最大的一个改进,就是将CCK模块核心化,在Drupal7里面,它的名字已经换成了Field,并成为Drupal7下面的核心必选模块。有了这个模块,我们就可以方便的为节点、评论、分类术语、用户添加扩展字段了,是的,它可以应用于节点、评论、分类术语、用户等等,而不像Drupal6下面的CCK那样只适用于节点类型。

 


Drupal版本:

1 自定义一个字段类型

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

    我们在本章,我们将通过创建一个自定义的字段类型,来学习Field API相关的各种知识。我们先来介绍一下这个模块的背景知识:

 

    我以前在给客户做网上书店的时候,就遇到过这样的需求,图书内容类型下面包含一个“拼音名称”字段,用来输入图书名称的拼音,那个时候我们采用了这样的解决办法,使用专门的软件,将书名转换为拼音,这方面有很多现有的工具可用,然后将这一信息导入到Drupal系统中来。那个时候我就在想,如果能够开发一个第三方模块,自动地生成拼音字段,就可以省去很多的麻烦。后来又遇到了这样的一个需求,对节点标题按照拼音的abcdz进行检索,此时如果我们自带了一个拼音字段,那么实现起来就会方便很多。这就是我们这个模块的实际的背景。

 

   实现所用的技术,我决定在Drupal7下面实现,然后采用定制一个字段类型的形式,实现节点标题的拼音字段。中文转拼音,的确有很多现成的PHP程序,但是大多数都不是开源的,我突然想到Transliteration模块,也可以完成中文转拼音这一工作。同时对这个模块又做了抽象,除了我们中文有这样的需求以外,日文、俄文等等,其它文字是否也有这样的需求。这就是我为什么选用Transliteration模块作为转换程序的原因,因为它用途更广,更国际化。

 

   我们需要创建一个字段,这个字段有一个对应的源字段,该字段的值,由源字段的值使用Transliteration模块转换而成,我们不需要负责它的输入。此时我所想到的就是,使用一个textfield就可以搞定这个字段。如果这样的话,其实使用现有的模块也可以解决问题,比如Computed Field模块,只需要把我们的转换代码放在对应的配置中,我想就可以解决问题了,当然我们此时仍然可以开发一个模块。后来我想到了另一个问题,那就是Transliteration模块的转换并不是100%准确的,我不知道其它语言的转换,但是对于中文来说,确实存在这样的问题。因此,这又多出来了一个需求,那就是允许用户手动地调整这个字段。

 


Drupal版本:

10 钩子hook_content_is_empty

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


    首先我们来判断一下,这个字段什么时候为空,让我们添加以下代码:

/**

 * Implements hook_content_is_empty().

 */

function transliteration_title_field_is_empty($item, $field) {

  if (empty($item['value'])) {

    return TRUE;

  }

  return FALSE;

}

    这段代码相当简单,我们这里没有考虑是否是手动输入,只考虑了文本框。当然用到字段为空的地方并不常见,特别是对于我们这个字段。这里我们主要顺带介绍一下这个hook_content_is_empty钩子函数,在这个钩子函数中,根据字段的输入值,进行判断,当字段为空时放回TRUE,否则返回FALSE。字段是否为空,有时候,对于一些特殊的字段来说,还是很有必要的。


Drupal版本:

11 钩子hook_field_validate

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

    接下来,我们需要检查用户的输入是否正确,我们主要检查用户手动输入的情况,看用户输入的字符是否是ASCII字符,因为经transliteration模块转换后的字符串只包含ASCII字符。我们来看看这个字段的验证函数:

/**

 * Implements hook_field_validate().

 *

 * Possible error codes:

 * - 'transliteration_title_invalid': The transliteration title is not valid

 */

function transliteration_title_field_validate($obj_type, $object, $field, $instance, $langcode, $items, &$errors) {


  foreach ($items as $delta => $item) {

//drupal_set_message($item['value']);

//[\x20-\x7e]   [A-Za-z0-9]

    if (!empty($item['manual']) && !preg_match('/^[\x20-\x7e]+$/', $item['value'])) {

      $message = t('"%value" is not a valid transliteration titles', array('%value' => $item['value']));

  /*

  //这段代码不能正常工作

      $errors[$field['field_name']][$langcode][$delta][] = array(

        'error' => "transliteration_title_invalid_".$field['field_name'],

        'message' => t('It is not a valid transliteration titles'),

      );

  */


  form_set_error($field['field_name'] .']['.$langcode.']['. $delta .'][value', $message);

    }

  }


}    

    这段代码的含义是,如果当前为手动输入,而用户输入的字符包含非ASCII字符时,设置一个错误信息。这里使用了正则表达式,来检查ASCII字符,我费了很大的功夫才找到了这个还能工作的表达式。另外,$errors[$field['field_name']][$langcode][$delta][]这种方式,工作起来总有问题,所以采用了更为原始一点的form_set_error,这在表单API里面讲过。注意$field['field_name'] .']['.$langcode.']['. $delta .'][value',在这个字符串的末位,没有“]”符号,对于这种数组形式的表单元素,就是这种用法。

 

    此时,如果在手动方式下,输入一个包含中文字符的字符串,系统就会报错,表单不能提交。如图所示:

   图片1.png

               当含有中文字符时,系统提示输入错误


Drupal版本:

12 钩子hook_field_presave

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

    我们完成了字段的验证以后,还有一项工作要做,那就是在将字段的值,保存到数据库之前,重新设置一下,在不是手动输入的情况下,将其设置为源字段经过transliteration模块转换后的值。为此,我们还需要实现一个钩子函数,在module文件中添加以下代码:

/**

 * Implements hook_field_presave().

 */

function transliteration_title_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {

//drupal_set_message($field['settings']['size']);

if($field['type'] == 'transliteration_title_field'){

  if(isset($entity->{$instance['widget']['settings']['source_field_id']})){

  $source_field = $entity->{$instance['widget']['settings']['source_field_id']};

if(is_array($source_field)){

  $source_field_items = $source_field[$langcode];

foreach ($source_field_items as $delta => $item) {

  if(empty($items[$delta]['manual'])){

       $upper_lower_case = $instance['widget']['settings']['upper_lower_case'];

if (function_exists($upper_lower_case)) {

  $items[$delta]['value'] = $upper_lower_case(transliteration_get($item['value']));

$items[$delta]['manual'] = 0;

}else{

  $items[$delta]['value'] = transliteration_get($item['value']);

$items[$delta]['manual'] = 0;

}

    

}

}

}else{

  if(empty($items[0]['manual'])){

  $upper_lower_case = $instance['widget']['settings']['upper_lower_case'];

if (function_exists($upper_lower_case)) {

//drupal_set_message('123');

//drupal_set_message( $upper_lower_case);

                     $items[0]['value'] = $upper_lower_case(transliteration_get($entity->{$instance['widget']['settings']['source_field_id']}));

  $items[0]['manual'] = 0;

}else{

  //drupal_set_message('123456');

$items[0]['value'] = transliteration_get($entity->{$instance['widget']['settings']['source_field_id']});

  $items[0]['manual'] = 0;

}

}

}

}

 

}

}

    这段代码,首先对源字段作了检查,假如默认的源字段为title,那么此时实体(节点)的title属性就是一个字符串值。如果是一个普通的字段,那么这里就会是一个数组。然后对于这两种情况,进行了分别处理。前者相对简单一点,后者稍微复杂了一点。这里面的核心代码是:

$items[$delta]['value'] = $upper_lower_case(transliteration_get($item['value']));

    这里我们对源字段的值进行了transliteration转换,然后根据我们的大小写情况,对字符串作了进一步处理,最后将返回值赋给了$items[$delta]['value']。这是最主要的代码,里面又细分了多种情况,但是逻辑基本上一致。

    此时,当我们编辑这个节点,取消手动输入,提交后,再回到编辑页面,我们看到系统为我们自动转换成了拼音。如图所示:

图片1.png 

          系统自动根据标题生成了拼音


13 钩子hook_field_formatter_info

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

    这就是我们想要的效果。不过,当我们访问节点页面时,并没有看到这个字段的内容,系统已经为其生成内容了阿。对于字段的显示,仍然需要由我们的模块来负责,谁让这个字段是由这个模块定义的呢。我们添加最后的两个钩子函数:

/**

 * Implements hook_field_formatter_info().

 *

 */

function transliteration_title_field_formatter_info() {

  $formats = array(

    'transliteration_title_default' => array(

      'label' => t('Default'),

      'description' => t('Default display for the transliteration title.'),

      'field types' => array('transliteration_title_field'),

    ),

    'transliteration_title_plain' => array(

      'label' => t('Plain text'),

      'description' => t('Display the transliteration title as plain text.'),

      'field types' => array('transliteration_title_field'),

    ),

  );

  return $formats;

}

 

/**

 * Implements hook_field_formatter_view().

 */

function transliteration_title_field_formatter_view($object_type, $object, $field, $instance, $langcode, $items, $display) {

  $element = array();

  switch ($display['type']) {

    case 'transliteration_title_default':

      foreach ($items as $delta => $item) {

  //drupal_set_message(print_r($item));

        $element[$delta] = array('#markup' => $item['value']);

      }

      break;

 

    case 'transliteration_title_plain':

      foreach ($items as $delta => $item) {

        $element[$delta] = array('#markup' => check_plain($item['value']));

      }

      break;

  }

  return $element;

}

    首先,我们实现了hook_field_formatter_info钩子函数,在这里我们定义了两个显示格式,一个是默认的格式,一个是纯文本的格式,本质上两者也没有太大的区别。此时清空缓存,然后访问字段的管理显示页面,我们会看到我们定义的字段格式,如图所示:

图片1.png 

                 我们定义的格式显示在了管理显示页面

     

     最后,我们在hook_field_formatter_view钩子中,定义了两种显示格式的具体实现,这里需要注意的是,这个钩子函数返回的是一个呈现数组。当我们再次访问节点页面时,就可以看到这个字段的值了。

图片1.png

我们的这个实例模块,到这里就讲解完毕,这里我将模块的实现,按照我们使用字段的先后顺序,逐一的剥离处理。希望读完这一节后,大家能对通过模块定义一个字段类型有所了解,对我们日常所用的字段有更深一层的把握。



Drupal版本:

14 验证已有的字段

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

    大多数字段模块,都自带了验证功能,但有时候,这些验证并不能满足我们实际的需求。对于常用的文本字段,其验证功能更弱。假如我们创建了一个book节点类型,为其添加了一个isbn文本字段,此时我们想对isbn作进一步的验证,假定验证规则为,如果输入的字符串的长度既不是10也不是13,那么我们就认为没有通过验证,同时假定当前语言为“und”。

    不妨将这个模块命名为isbn_validation,我们来看看这个模块的主代码:

 

/**

 * Implements hook_field_attach_validate().

 */

function isbn_validation_field_attach_validate($entity_type, $entity, &$errors) {

list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);

 

if($entity_type =='node' &&  $bundle == 'book'){

  $field_isbn_length = strlen($entity->field_isbn['und'][0]['value']); 

  if(($field_isbn_length != 10) && ($field_isbn_length != 13)){

  $errors['field_isbn']['und'][0][] = array(

  'error' => 'field_isbn', 

  'message' => t('无效的ISBN.'),

  );

  }  

}

}

 

    对于其它模块定义的字段,我们想对其进行验证的话,可以使用hook_field_attach_validate,在上面的代码中,我们对文本字段field_isbn按照我们的验证进行了检查,如果没有通过,则设置错误信息。注意这里的$errors是通过引用传递的,如果它不为空,表单就通不过验证。这里的结构为:

   $errors[字段名][当前语言][delta][]

   而右边数组值,则包含两个键,一个是'error',表示错误代码;一个是 'message',表示出现错误时显示给用户的错误提示。

    

    通过编写代码,进行验证,对于不懂程序的人来说,麻烦了很多。不过drupal.org上有一个第3方模块“Field validation”,可以方便的为每个字段实例添加正则表达式规则验证,可以帮我们解决常见的验证问题。项目地址:http://drupal.org/project/field_validation


Drupal版本:

15 伪字段

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

    如果你玩过三国杀,并且还比较熟悉的话,那么你一定了解里面的一个武将,袁术,他最常见的技能就是能够多摸几张牌,相信这个很多人都熟悉,他还有一个技能,叫做“伪帝”,就是说他具有当前主公的主公技,但是他本身不是主公。我这里借用一下,将显示在管理字段页面的非字段称之为“伪字段”,表示它们本身不是字段,但是又具有字段的一些属性。

    

     有些信息,比如节点的标题,它的实现方式没有采用Field API的形式,但是为了管理的方便,Drupal也将其显示在了管理字段界面,Drupal是怎么实现这一点的呢?通过node.module我们可以找到对应的代码:  

   

/**

 * Implements hook_field_extra_fields().

 */

function node_field_extra_fields() {

  $extra = array();

 

  foreach (node_type_get_types() as $type) {

    if ($type->has_title) {

      $extra['node'][$type->type] = array(

        'form' => array(

          'title' => array(

            'label' => $type->title_label,

            'description' => t('Node module element'),

            'weight' => -5,

          ),

        ),

      );

    }

  }

 

  return $extra;

}

 

    节点模块就是通过这段代码,将节点的标题显示在了字段管理界面,注意这里将'weight'属性设置为了-5,所以节点的标题默认总是显示在其它字段的上面,如图所示。

图片1.png 

             在管理字段页面,节点标题总是显示最前面

 

    如果熟悉Ubercart模块的话,我们知道,在这个电子商务模块中,产品属性并没有实现为字段的形式,或许将来会朝这方面发展,它的竞争对手Commerce模块已经完全采用了字段的形式了。在UbercartDrupal7版本里面,产品属性就采用了伪字段的形式,下面是该模块的对应代码,这里我们有删减:

function uc_product_field_extra_fields() {

  $extra = array();

 

  foreach (uc_product_types() as $type) {

    $extra['node'][$type] = array(

      'display' => array(

        'display_price' => array(

          'label' => t('Display price'),

          'description' => t('High-visibility sell price.'),

          'weight' => -1,

        ),

 

       ……

 

        'add_to_cart' => array(

          'label' => t('Add to cart form'),

          'description' => t('Add to cart form'),

          'weight' => 10,

        ),

      ),

    );

  }

 

  return $extra;

}

    当然,这些伪字段并不是具有所有字段的属性,比如在字段的管理显示页面,就没有出现这些伪字段。

 

 


Drupal版本:

16 为已有字段定制格式器

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

 

    有时候,一个字段模块自己提供的格式器,可能会出现不够用的情况,比如说图片字段,在Drupal7里面,它的格式器可以用来显示原始图片、各种缩略图,但是无法显示图片的链接。如果使用views模块的时候,想输出图片字段的URL,默认是不可能的,当然我们可以通过定制views的模板文件,来输出图片的URL。如果我们能够为图片提供一个URL路径格式器,就可以解决上述的问题了。

 

    是的,在drupal.org上就存在着这样的一个第三方模块“Image URL Formatter”,专门来解决这个问题的,这个项目地址是http://drupal.org/project/image_url_formatter我们来学习一下这个模块,这个模块的代码大部分是从image模块中直接拷贝过来的。

 

/**

 * Implements hook_field_formatter_info().

 */

function image_url_formatter_field_formatter_info() {

  $formatters = array(

    'image_url' => array(

      'label' => t('Image URL'),

      'field types' => array('image'),

      'settings' => array('image_style' => '', 'image_link' => ''),

    ),

  );

 

  return $formatters;

}

    我们在这里为image字段定义了一个新的格式器“Image URL”,顾名思义,它就是用来输出图片的URL路径的。

 

/**

 * Implements hook_field_formatter_settings_form().

 */

function image_url_formatter_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {

  $display = $instance['display'][$view_mode];

  $settings = $display['settings'];

 

  $image_styles = image_style_options(FALSE);

  $element['image_style'] = array(

    '#title' => t('Image style'),

    '#type' => 'select',

    '#default_value' => $settings['image_style'],

    '#empty_option' => t('None (original image)'),

    '#options' => $image_styles,

  );

 

  $link_types = array(

    'content' => t('Content'),

    'file' => t('File'),

  );

  $element['image_link'] = array(

    '#title' => t('Link image url to'),

    '#type' => 'select',

    '#default_value' => $settings['image_link'],

    '#empty_option' => t('Nothing'),

    '#options' => $link_types,

  );

 

  return $element;

}

    这里的定义和核心image模块中的对应实现完全一致。我们创建一个节点类型,并为其添加图片字段,然后启用这个模块,在管理显示页面,假定我们选择了Image URL作为显示格式,如图所示: 

 

图片1.png 

                        图片在管理显示页面的对应项

 

     当我们点击最右边的配置按钮时,就会出现更多的配置选项,如图所示:

图片2.png 

                       格式器自带的设置项

     上述代码就对应于右边的两个配置项。这下我们明白了钩子hook_field_formatter_settings_form是用来做什么的。在这个钩子里面,API函数image_style_options值得学习一下,它用来获取图片的样式选项。

 

/**

 * Implements hook_field_formatter_settings_summary().

 */

function image_url_formatter_field_formatter_settings_summary($field, $instance, $view_mode) {

  $display = $instance['display'][$view_mode];

  $settings = $display['settings'];

 

  $summary = array();

 

  $image_styles = image_style_options(FALSE);

  // Unset possible 'No defined styles' option.

  unset($image_styles['']);

  // Styles could be lost because of enabled/disabled modules that defines

  // their styles in code.

  if (isset($image_styles[$settings['image_style']])) {

    $summary[] = t('URL for image style: @style', array('@style' => $image_styles[$settings['image_style']]));

  }

  else {

    $summary[] = t('Original image URL');

  }

 

  $link_types = array(

    'content' => t('Linked to content'),

    'file' => t('Linked to file'),

  );

  // Display this setting only if image is linked.

  if (isset($link_types[$settings['image_link']])) {

    $summary[] = $link_types[$settings['image_link']];

  }

 

  return implode('<br />', $summary);

}

    上述代码就对应于图片字段,最右边配置按钮前面的那段描述,如图所示:

 

 

         格式器具体配置的描述

     这个钩子hook_field_formatter_settings_summary,就是为格式器的当前具体配置,返回一个简洁的描述,方便用户浏览。

 

/**

 * Implements hook_field_formatter_view().

 */

function image_url_formatter_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {

  $element = array();


  switch ($display['type']) {

    case 'image_url':

  // Check if the formatter involves a link.

      if ($display['settings']['image_link'] == 'content') {

        $uri = entity_uri($entity_type, $entity);

      }

      elseif ($display['settings']['image_link'] == 'file') {

        $link_file = TRUE;

      }

 

      foreach ($items as $delta => $item) {

        if (isset($link_file)) {

          $uri = array(

            'path' => file_create_url($item['uri']),

            'options' => array(),

          );

        }

    //debug($item);

        $element[$delta] = array(

          '#theme' => 'image_url_formatter',

          '#item' => $item,

          '#image_style' => $display['settings']['image_style'],

          '#path' => isset($uri) ? $uri : '',

        );

      }

 

      break;

 

  }

 

  return $element;

}

   在这段代码中,我们返回了图片的呈现数组,在这个呈现数组的每个delta项中,对应的主题函数替换为了theme_image_url_formatter,而不是image模块里面的theme_image_formatter。这里面,API函数file_create_url用来把uri转换为URLuri就是文件在Drupal里面存储的路径信息,但是这种信息,都是这种形式的“public://img/my123.jpg”,我们需要使用这个函数将其转换一下。

/**

 * Implements hook_theme().

 */

function image_url_formatter_theme() {

  return array(

    'image_url_formatter' => array(

      'variables' => array(

        'item' => NULL, 

'path' => NULL, 

'image_style' => NULL,

      ),

    ),

  );

}

 

/**

 * Returns HTML for an image url field formatter.

 *

 * @param $variables

 *   An associative array containing:

 *   - item: An array of image data.

 *   - image_style: An optional image style.

 *   - path: An array containing the link 'path' and link 'options'.

 *

 * @ingroup themeable

 */

function theme_image_url_formatter($variables) {

  $item = $variables['item'];

  $image = array(

    'path' => $item['uri'],

    'alt' => $item['alt'],

  );

  // Do not output an empty 'title' attribute.

  if (drupal_strlen($item['title']) > 0) {

    $image['title'] = $item['title'];

  }

  $output = file_create_url($item['uri']);

  if ($variables['image_style']) {

//debug($image);

    $image['style_name'] = $variables['image_style'];

$output = image_style_url($image['style_name'], $item['uri']);

  }

  if ($variables['path']) {

    $path = $variables['path']['path'];

    $options = $variables['path']['options'];

    // When displaying an image inside a link, the html option must be TRUE.

    $options['html'] = TRUE;

    $output = l($output, $path, $options);

  }

 

  return $output;

}

 

     在这个主题函数中,我们返回的是图片的URL,而不是带有img标签的图片了。整个模块,全部都是复制拷贝image模块的代码,只有这里在逻辑上作了修改。image_style_url用来获取图片特定样式下对应图片的URL的。有时候,我们会用到这个函数。

 

     我们看到,为一个已有的字段定义一个新的格式器,并不麻烦,而且很多代码都可以从原有模块中复制过来。采用这种方式的好处是,这个格式器定义好了以后,很多人都可以用,虽然定义的时候稍微麻烦了一点,但是以后会方便很多,送人玫瑰,手留余香。

 


Drupal版本:

17 总结

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

通过本章的学习,你应该可以:

自定义一个字段模块

为已有字段添加验证

了解什么是伪字段

为已有字段定制格式器

 

 


 

                


Drupal版本:

2 准备工作

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

    现在让我们实际的构建这个模块,我们不妨把这个模块的中文名字叫做“标题拼音字段”,英文名字叫做“Transliteration title”,这样更国际化一点。我们在sites\all\modules\custom目录下,创建一个名为transliteration_title的文件夹,向里面添加两个文件transliteration_title.infotransliteration_title.module。接着向info文件中添加以下信息:

    

name = Transliteration title

description = Transliteration title field.

core = 7.x

dependencies[] = transliteration

    注意,我们这里使用了依赖关系,表示这个模块依赖于transliteration模块。如果没有安装于transliteration模块,并且这个模块现在还不存在于我们的站点目录下,那么我们就无法启用我们的这个自定义模块。

 


Drupal版本:

3 钩子hook_field_info

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

    接下来,我们向module文件中添加以下代码:

<?php

 

 

/**

 * Implements hook_field_info().

 */

function transliteration_title_field_info() {

  return array(

    'transliteration_title_field' => array(

      'label' => 'Transliteration title',

      'description' => t('This field stores and renderes transliteration title.'),

      'instance_settings' => array(

        'size' => 255,

        'upper_lower_case' => 'strtolower',

        'source_field_id' => 'title',

      ),

      'default_widget' => 'transliteration_title_field',

      'default_formatter' => 'transliteration_title_default',

    ),

  );

}

 

    我们在这里实现了hook_field_info(),在这个钩子中,我们返回了一个关联数组,通过这个关联数组,我们定义了一个新的字段transliteration_title_field,它包含以下键:

    label:表示这个字段的名字。

    description表示这个字段的描述。

    instance_settings表示这个字段实例的配置,这里给出的是默认配置,在创建好该字段后,可以修改这一配置,这个配置中的键是随意的,取决于你的实际需要。这里我们包含了三个键,size是默认textfield的大小,upper_lower_case表示字符串大小写的情况,   source_field_id表示源字段的机读名字。

    default_widget:默认的字段输入控件。这里为transliteration_title_field

    default_formatter:默认的字段显示格式器。这里为transliteration_title_default

 

    此时,如果我们启用这个模块,然后进入一个内容类型的管理字段页面,在字段类型的选择列表中,还找不到我们定义的这个字段,如图所示:

图片1.png 

            我们新建的字段类型还没有显示出来

 

    除了实现hook_field_info()钩子以外,我们还需要实现多个钩子,才能让这个模块工作起来。我们按照这样的一个顺序,添加字段、配置字段、输入字段、显示字段,这样的一个顺序来开发我们的模块,这也是我们使用一个字段类型的通常顺序。

 


Drupal版本:

4 钩子hook_field_widget_info

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

   让我们实现另一个钩子,向module文件中添加以下代码:

 

/**

 * Implements hook_field_widget_info().

 */

function transliteration_title_field_widget_info() {

  return array(

    'transliteration_title_field' => array(

      'label' => t('Transliteration title'),

      'field types' => array('transliteration_title_field'),

'multiple values' => FIELD_BEHAVIOR_DEFAULT,

    ),

  );

}

    这里我们定义了这个字段的输入控件,字段输入控件的名字,和字段的名字这里是一样的,在Drupal7里面,很多字段类型都采用了这种习惯。当然,一个字段可以有多个控件类型,我们这里只有一个而已。此时,当我们再次访问一个内容类型的管理字段页面时,我们就可以添加“Transliteration title”字段了,如图所示:

 

图片1.png 

          字段类型“Transliteration title”显示了出来

    我们看到,如果我们想要在管理字段页面显示出来我们的新建字段,我们需要实现两个钩子hook_field_info()hook_field_widget_info()

   我们创建一个测试节点类型,比如“test type”,然后向该内容类型添加一个名为“Test”的“Transliteration title”字段,此时一切都能正常工作。但是,当我们配置这个字段,没有我们想要的配置字段,当我们想为这个字段输入内容时,我们发现在节点的输入表单中找不到这个字段。当我们检查这个字段创建的数据库表时,我们发现它并没有包含用来保存用户输入的列,如图所示:

图片2.png 

           字段对应表field_data_field_test的结构

 


Drupal版本:

5 钩子hook_field_schema


    为此,我们首先需要在添加这个字段时,能够正确地创建我们想要的表结构,对于这个字段,我们需要保存两个值,一个是转换后的字符串,我们不妨采用默认的value;一个表示是否是手动输入的,我们这里使用manual

 

    接下来,让我们添加一个transliteration_title.install文件,然后向该文件中添加以下内容:

<?php

 

/**

 * @file

 * Install file for the transliteration title module.

 */

 

/**

 * Implements hook_field_schema().

 */ 

function transliteration_title_field_schema($field) {

  return array(

    'columns' => array(

      'value' => array(

        'type' => 'varchar',

        'length' => 255,

        'not null' => FALSE,

        'sortable' => TRUE,

      ),

      'manual' => array(

        'type' => 'int',

'not null' => TRUE,

        'default' => 0,

      ),

    ),

  );

}

 

    注意,这个钩子和hook_schema有点类似,它是专门用于定义字段的表结构的。这里我们按照前面的要求,定义了两列:'value''manual'

    让我们删除以前添加的“Test”字段,再重新创建一遍。我们来看一下新字段对应数据库表的表结构,如图所示:

图片1.png 

         字段对应表field_data_field_test的结构

    注意,transliteration_title_field_schema里面的'value''manual',对应于这个表结构中的field_test_valuefield_test_manual。表结构中的其余列,在所有字段的field_data表中都是通用的。这里前缀“field_test”就是我们这个字段的机读名字。我们来看看对应关系:

    “field_test” _” value  = field_test_value

    “field_test” _” “manual”  = field_test_manual

     字段名      +         列名

字段对应表名的命名规则:

    “field_data” _” field_test”  field_data_field_test

    “field_data” _” body”  field_data_body

     “field_data” 字段名

 

    如果我们想要读取我们这个字段中的值,假定当前语言为未定义语言,也就是“und”:

$node->field_test['und'][0]['value'];

$node->field_test['und'][0]['manual'];

    这里需要注意的是,并不是所有的字段,都采用“value”存储自己的值,比如分类术语字段,采用的就是“tid,获取分类术语字段的值,所用的代码就是:

$node->field_myfield['und'][0]['tid'];

而不是

$node->field_myfield['und'][0]['value'];


Drupal版本:

6 钩子hook_field_widget_settings_form

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

    接下来,让我们看一下,如何为字段实例添加自己的设置,在我们这里,不需要实现钩子hook_field_settings_form,我们只需要在字段控件的设置表单中,添加自己的设置即可。

 

/**

 * Implements hook_field_widget_settings_form().

 */

function transliteration_title_field_widget_settings_form($field, $instance) {

  $widget = $instance['widget'];

  $settings = $widget['settings'];


$form['size'] = array(

    '#type' => 'textfield',

    '#title' => t('Size of textfield'),

    '#default_value' => isset($settings['size'])?$settings['size']:'255',

    '#required' => TRUE,

    '#element_validate' => array('_element_validate_integer_positive'),

  );


$form['source_field_id'] = array(

    '#type' => 'textfield',

    '#title' => t('Source field ID'),

    '#default_value' => isset($settings['source_field_id'])?$settings['source_field_id']:'',

    '#required' => TRUE,

  );


$form['upper_lower_case'] = array(

    '#type' => 'select',

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

    '#default_value' => isset($settings['upper_lower_case'])?$settings['upper_lower_case']:'',

    '#required' => TRUE,

'#options' => array(

  'strtolower' => 'Lower case',

'strtoupper' => 'Upper case',

'ucfirst' => 'Capitalize first letter',

'ucwords' => 'Capitalize each word',

),

  );

  return $form;

}

    在hook_field_widget_settings_form钩子实现里面,返回的是一个普通的表单数组,里面包含配置项对应的表单元素。在这里,我们定义了三个配置项,文本栏的尺寸、源字段ID、大小写情况。添加了上述代码以后,重新刷新字段的编辑页面,我们看到上面显示了我们定义的三个字段,如图所示:

 

图片1.png 

                    我们定义的配置项在字段的编辑页面显示了出来

 

    当提交这个表单时,配置选项的值会保存到控件设置里面,我们在后面会用到这里的设置。


Drupal版本:

7 钩子hook_field_widget_form

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

    现在,该向字段里面输入信息了,此时,我们点击创建节点链接,来创建一个该类型下面的一个节点,此时在添加节点的表单中,并没有我们这个字段对应的表单元素。现在就让我们定义具体的输入控件,向module文件里面添加以下代码:

 

/**

 * Implements hook_field_widget_form().

 */

function transliteration_title_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {

  $element += array(

    '#type' => $instance['widget']['type'],

    '#default_value' => isset($items[$delta]) ? $items[$delta] : '',

  );

  return $element;

}


Drupal版本:

8 钩子hook_element_info

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

    这段代码看起来有点简单,加进来以后,并没有得到我们想要的效果,是的。我们还需要实现更多一点的钩子。在Drupal的表单元素中,部分表单元素是由其它表单元素复合而成的,比如datefile,以及一些第三方的表单元素。其实我们可以把我们的这个输入控件,定义成为一个表单元素类型,这样更有利于复用。我们来看看这个表单元素类型的定义:

 

/**

 * Implements hook_element_info().

 */

function transliteration_title_element_info() {

  $elements = array();

  $elements['transliteration_title_field'] =  array(

    '#input' => TRUE,

    '#process' => array('transliteration_title_field_process'),

    '#theme' => 'transliteration_title_field',

    '#theme_wrappers' => array('form_element'),

  );

  return $elements;

}

 

/**

 * Process the transliteration_title type element before displaying the field.

 *

 * Build the form element. When creating a form using FAPI #process,

 * note that $element['#value'] is already set.

 *

 * The $fields array is in $complete_form['#field_info'][$element['#field_name']].

 */

function transliteration_title_field_process($element, $form_state, $complete_form) {

  $settings = &$form_state['field'][$element['#field_name']][$element['#language']]['instance']['settings'];

  $element['value'] = array(

    '#type' => 'textfield',

    '#maxlength' => 255,

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

    '#required' => isset($element['#required']) ? $element['#required'] : FALSE,

    '#default_value' => isset($element['#value']['value']) ? $element['#value']['value'] : NULL,

  );

  $element['manual'] = array(

'#type' => 'checkbox',

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

'#default_value' => isset($element['#value']['manual']) ? $element['#value']['manual'] : NULL,

);

  return $element;

}

    我们在前面讲表单元素的时候,就提到过hook_element_info(),这里我们使用这个钩子函数定义了一个自己的表单元素类型,为它指定了一个定制的处理函数,在transliteration_title_field_process里面,我们看到这个表单元素是由一个文本字段和一个复选框复合而成的。

    让我们清空缓存,然后刷新添加节点的表单页面,此时我们看到了新增的这个表单元素了。它和普通的表单元素没有太大的区别,如图所示:

图片1.png 

              transliteration_title_field表单元素(输入控件)


Drupal版本:

9 对应表单元素的主题函数

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

     对于表单元素,我们可以为其定义专门的主题函数,用来控制其外观,现在添加主题相关的代码,如下所示:

/**

 * Implements hook_theme().

 */

function transliteration_title_theme() {

  return array(

    'transliteration_title_field' => array(

      'render element' => 'element',

    ),

  );

}

 

/**

 * FAPI theme for an individual text elements.

 */

function theme_transliteration_title_field($vars) {

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

 

  $element = $vars['element'];

 

  $output = '';

  $output .= '<div class="transliteration-title-field clearfix">';

  $output .= '<div class="transliteration-title-field-value">'. drupal_render($element['value']) .'</div>';

  $output .= '<div class="transliteration-title-field-manual">'. drupal_render($element['manual']) .'</div>';

  $output .= '</div>';

  return $output;

}

 

   这里我们实现了hook_theme,在里面注册了theme_transliteration_title_field函数,在后者的具体实现里面,我们为表单元素添加了特有的html标签,用来控制CSS样式。当然,我们这里面并没有添加任何的CSS,所以看起来样式没有什么变化。

    

    现在我们该考虑一下如何处理用户的输入了,也就是需要对用户的输入进行验证,把这个字段的输入,正确的保存到数据库中。当然现在也是可以工作的,但是还没有按照我们的要求工作。


Drupal版本: