Coding custom (compound) Drupal fields

Feb 19, 2015

Joris Snoek - Business Dev
+31 (0)20 - 261 14 99

In a previous post I wrote about why 'Compound fields' in your Drupal installation. A compound field can be seen as a unified field, so a field that contains multiple fields. Get it? :)

And now, as promised: how to build

To clarify you will find below an example of how to build a module in which you define a compound field. In this example I am creating a 'Video' field’ for which the following two fields are required:

• Video URL (video_url)
• A Drupal file ID for the preview (video_preview_fid)

Compound fields can be created by manually coding fields in a Drupal module. In this example I assume you have previously developed modules yourself and the module will be built up in three files:

  1. .info
  2. .install
  3. .module

1) The .info file

Initialize the module

​name = Custom Fields videodescription = Contains the video compound field.package = Lucius Example Packagecore = 7.x

2) The .install file

Then you will need database fields to save the data that will be entered in the fields. These fields can be created using the .install file

<?php/*** @file*  This file contains the schema for the video fields module.*//*** Implements hook_field_schema().*/function cl_fields_video_field_schema($field) {  return array(    'columns' => array(      'video_id' => array(        'description' => 'The primary identifier for a video.',        'type' => 'serial',        'unsigned' => TRUE,        'not null' => TRUE,      ),      'video_url' => array(        'type' => 'varchar',        'length' => '2048',        'not null' => FALSE,      ),      'video_preview_fid' => array(        'type' => 'int',        'size' => 'small',        'not null' => TRUE,        'default' => 0,      ),    ),    'indexes' => array(      'video_id' => array('video_id'),    ),    'foreign keys' => array(      'video_preview_fid' => array(        'table' => 'file_managed',        'columns' => array('fid' => 'video_preview_fid'),      ),    ),  );}

3) The .module file

Define the field for Drupal:

/*** Implements hook_field_info().*/function cl_fields_video_field_info() {  return array(    'cl_fields_video' => array(      'label' => t('Video'),      'description' => t("This field stores video's and their previews."),      'settings' => array('allowed_values' => array(), 'allowed_values_function' => ''),      'default_widget' => 'cl_fields_video',      'default_formatter' => 'cl_fields_video',      'property_type' => 'cl_fields_video',      'property_callbacks' => array('cl_fields_video_property_info_callback'),    ),  );}

Define the validation for the entered data:

/*** Implements hook_field_validate().*/function cl_fields_video_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {  global $user;  if ($field['type'] == 'cl_fields_video') {    foreach ($items as $delta => $item) {      // Check if we have an image, if we do make it permanent.      if (!empty($item['video_preview_fid'])) {        $file = file_load($item['video_preview_fid']);        // Change status to permanent.        $file->status = FILE_STATUS_PERMANENT;        // Save.        file_save($file);        // Record that the module is using the file.        file_usage_add($file, 'cl_fields_video', 'general', $user->uid);      }    }  }}

Define hook_field_is_empty()

This determines if the field can be empty.

Some fields are optional. Or, what is also possible, one field or the other needs to be filled in.
If this feature true returned, the field will not be saved.

/*** Implements hook_field_is_empty().*/function cl_fields_video_field_is_empty($item, $field) {  if ($field['type'] == 'cl_fields_video') {    return empty($item['video_url']);  }}

Define the Drupal widget information:

/*** Implements hook_field_widget_info().*/function cl_fields_video_field_widget_info() {  return array(    'cl_fields_video' => array(      'label' => t('Video and preview field'),      'field types' => array('cl_fields_video'),    ),  );}

Define the fields:

/*** Implements hook_field_widget_form().*/function cl_fields_video_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {  switch ($instance['widget']['type']) {    // Compound field for video urls and their preview.    case 'cl_fields_video':      // The fields to be rendered.      $fields = array(        'video_url' => t('Video url'),        'video_preview_fid' => t('Video preview image'),      );      // Loop through each field and create the appropriate widget.      foreach ($fields as $key => $label) {        switch($key) {          case 'video_url':            $element[$key] = array(              '#type' => 'textfield',              '#title' => $label,              '#default_value' => isset($items[$delta][$key]) ? $items[$delta][$key] : '',            );            break;          case 'video_preview_fid':            $element[$key] = array(              '#type' => 'managed_file',              '#upload_location' => 'public://video_previews',              '#progress_indicator' => "bar",              '#title' => $label,              '#default_value' => isset($items[$delta][$key]) ? $items[$delta][$key] : 0,            );            // Add the validators            $supported_extensions = array('png', 'gif', 'jpg', 'jpeg');            $element[$key]['#upload_validators']['file_validate_extensions'][0] = implode(' ', $supported_extensions);            break;          default:            break;        }      }    break;  }  return $element;}

Define hook_field_formatter_info()

This let’s you determine the frontend display of your compound field. In this case the type cl_fields_video. Then you create the real shape with hook_field_formatter_view()

Similar to hook_block_info() for logging in and hook_block_view for the display.

/*** Implements hook_field_formatter_info().*/function cl_fields_video_field_formatter_info() {  return array(    'cl_fields_video' => array(      'label' => t('Video'),      'field types' => array('cl_fields_video'),    ),  );}

Define hook_field_formatter_view()

/*** Implements hook_field_formatter_view().*/function cl_fields_video_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {  $element = array();  $node = menu_get_object();  if (!empty($node)) {    switch ($display['type']) {      case 'cl_fields_video':        // Theme items.        foreach ($items as $key => $item) {          $video = array(            'video_title' => $node->title . ' ' . t('video') . ' ' . ($key + 1),            'video_url' => $item['video_url'],          );          // Add preview if available.          if (!empty($item['video_preview_fid'])) {            $video['video_preview'] = cl_helpers_get_full_path($item['video_preview_fid']);          }          $element[$key] = array('#markup' => theme('cl_metadata_video', $video));        }        break;    }  }  return $element;}

Define the Meta data

When you get to work with compound fields and continue to code your system then there will probably come a time when you will make use of the meta data wrapper feature to perform CRUD actions with data in the fields.

This feature ensures that your custom compound field therein is available:

/*** Custom callback function for metadata.** @param $info* @param $entity_type* @param $field* @param $instance* @param $field_type*/function cl_fields_video_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {  $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];  $property['getter callback'] = 'entity_metadata_field_verbatim_get';  $property['setter callback'] = 'entity_metadata_field_verbatim_set';  unset($property['query callback']);  $property['property info']['video_url'] = array(    'type' => 'text',    'label' => t('Video url'),    'setter callback' => 'entity_property_verbatim_set',  );  $property['property info']['video_preview_fid'] = array(    'type' => 'text',    'label' => t('Video preview fid'),    'setter callback' => 'entity_property_verbatim_set',  );}

Download example code

Do you want to download an example? Click here.

Wrap up

Ok, that's it for now. Please let me know if you have any feedback or questions.

-- Regards, Thomas

Comments

Stay up-to-date

Subscribe to our Lucius newsletter
and be the first to know about new articles!

Need even
more knowlegde?