Login

Drupal 7 Field API - A simple example

The following example uses Drupal 7 beta 3 release. A simple introduction and demonstration of the new Field API (CCK rolled in the Core Drupal API).

When you install Drupal 7 beta 3,  the system is pre-configured with several base field types (e.g. Decimal, Boolean, Integer, Image. etc). An adminstrator add one of the forementioned field types to a content type (e.g. Story or Page) and thus create a "custom" content type. An admistrator simply uses the adminstrative user interface (web forms) to create a custom content type. So far, no programming required.

For our example, we decided to add a new field type, using the new Drupal 7 Field API. We decided to call our field "News Date". It's a simple field that is stored in the database as an integer (number of seconds), The field is displayed in M-d-Y format (e.g. Nov-17-2010). When you create or edit the "News Date" field, the system displays the standard Drupal 3 drop down combo boxes (month, date, year).

To create our module. we start by creating a new folder (news_date) with 3 files: news_date.info, news_date.install and news_date.module. 

We define the database storage in the news_data.install file:

 
<?php
 
    function news_date_field_schema($field) {
       return array(
         'columns' => array(
         'news_date' => array(
            'type' => 'int',
            'not null' => TRUE,
            'default'  => 0,
            ),
         ),
     );
   }

 

We create a simple info file:

name = News Date Field
description = A Simple Date Field for Drupal 7
package = public-action fields
version = VERSION
core = 7.x
files[] = news_date.module
files[] = news_date.install

And finally a simple module file which implements a few hooks:

<?php
 
/**
 * Implements hook_field_info().
 */
function news_date_field_info() {
  return array(
    'news_date' => array(
      'label' => t('News  Date'),
      'description' => t('A Date Time Period'),
      'default_widget' => 'news_date_widget',
      'default_formatter' => 'news_date_formatter',
    ),
  );
}
 
 
/**
 * Implements hook_field_formatter_info().
 */
function news_date_field_formatter_info() {
  return array(
      'news_date_formatter' => array(
          'label' => t('Simple News Date formatter'),
          'field types' => array('news_date'),
      ),
  );
}
 
 
/**
 * Implements hook_field_formatter_view().
 */
function news_date_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array();
 
  switch ($display['type']) {
    case 'news_date_formatter':
      foreach ($items as $delta => $item) {
           if ($item['news_date']) {
                $formattedDate = _dateToString($item['news_date']);
                $element[$delta]['#markup'] = '<span>' . $formattedDate .'</span>';
            }
      }
      break;
  }
  return $element;
}
 
/**
 * Implements hook_field_widget_form().
 */
function news_date_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  $field_name = $field['field_name'];
  $field_type = $field['type'];
 
  $default_date_int = $instance['default_value'][0][$field_type][$field_type];
  $default_date_array = _intToDateArray($default_date_int);
 
  $news_data_value = isset($default_date_array) ? $default_date_array : _intToDateArray(time());
 
  if (isset($form_state['input'][$field_name][$langcode][$delta][$field_type]['day'])) {
    $day = $form_state['input'][$field_name][$langcode][$delta][$field_type]['day'];
    $month = $form_state['input'][$field_name][$langcode][$delta][$field_type]['month'];
    $year = $form_state['input'][$field_name][$langcode][$delta][$field_type]['year'];
    $news_data_value = array('year' => $year, 'month' => $month, 'day' => $day);
  }
 
  switch ($instance['widget']['type']) {
    case 'news_date':
 
      $element['news_date'] = array(
          '#type' => 'date',
          '#title' => $element['#title'],
          '#description' => $element['#description'],
          '#default_value' => $news_data_value,
          '#required' => $element['#required'],
          '#weight' => isset($element['#weight']) ? $element['#weight'] : 0,
          '#delta' => $delta,
          '#element_validate' => array('strToDateValidate'),
      );
 
      break;
  }
  return $element;
}
 
/**
 * Implements hook_field_widget_error().
 */
function news_date_widget_error($element, $error, $form, &$form_state) {
  switch ($error['error']) {
    case 'news_date_invalid':
      form_error($element, $error['message']);
      break;
  }
}
 
 
/**
 * Implements hook_field_is_empty().
 */
function news_date_field_is_empty($item, $field) {
   if (empty($item['news_date'])) {
        return true;
   }
}
 
/**
 * Implements hook_field_widget_info().
 */
function news_date_field_widget_info() {
  return array(
    'news_date' => array(
      'label' => t('News Letter Date'),
      'field types' => array('news_date'),
    ),
  );
}
 
/**
 * Utility function which converts the 3 elements, month, day and year
 * in to a unix timestamp (number of seconds) 
 */
function strToDateValidate($element, &$form_state) {
  if (isset($element['#value'])) {
     $day = $element['#value']['day'];
     $month = $element['#value']['month'];
     $year = $element['#value']['year'];
     $value = mktime(0, 0, 0, $month, $day, $year);
     form_set_value($element, array('news_date' => $value), $form_state);
  }
}
/**
 * Utility function which converts a string representation of a unix timestamp
 * and converts to a human readable format.
 */
function _dateToString($dateValue) {
  $str = "Unable to Format Date";
  try {
   $timestamp = intval($dateValue);
   $str = format_date($timestamp, 'custom', 'M-d-Y');
  } catch (Exception $e) {
     $str = "Unable to Format Date" . $e->getMessage();
  }
  return $str;
}
 
 
/**
 * Utility function which converts a string representation of a unix timestamp
 * and converts to an array suitable for the Drupal date widget (3 drop down combo 
 * boxes, month, day, year).
 */
function _intToDateArray($timestr) {
  $timestamp = intval($timestr);
  $year = date('Y', $timestamp);
  $month = date('m',$timestamp);
  $day = date('d',$timestamp);
  return array('year' => $year, 'month' => $month, 'day' => $day);
}

Quick explanation:

The news_date.install file includes a hook_field_schema implementation. The implementation tells drupal to store our field value in the database as an integer.

The rest of the methods simply convert the integer (news_date_field_formatter_view()) to a date for display.

news_date_field_widget_form() instructs Drupal to use a "date" widget when creating or editing our field.

The statement '#element_validate' => array('strToDateValidate'), instructs Drupal to convert the widget data (month, day, year) back to an integer (number of seconds).

Comments

Few remarks

Unpublished

Hi,

Nice script and basis to build on. A few remarks:

I'd rather use the current date instead of a default date and want to be able to retrieve a previous saved date from the db, so I changed news_date_field_widget_form() from:

$default_date_int = $instance['default_value'][0][$field_type][$field_type];
$default_date_array = _intToDateArray($default_date_int);
$news_data_value = isset($date_array) ? $date_array : _intToDateArray(time());

to

$default_date_int = isset($items[$delta][$field_type]) && !is_array($items[$delta][$field_type]) ? $items[$delta][$field_type] : time();
$news_data_value = _intToDateArray($default_date_int);

But also, now I load previous set dates, the did not get selected in the HTML select element, because _intToDateArray() returned month and day numbers with leading zeros. So I changed this function from:

$month = date('m',$timestamp);
$day = date('d',$timestamp);

to

$month = date('n',$timestamp);
$day = date('j',$timestamp);

Nice way to learn how to make custom fields! :-)

Cheers!

Laurens Meurs, Rotterdam

Nice tutorial

Nice tutorial, but how do you associate it to a node / entity? Could you add a tutorial on how to add this new field to something please. I assume you use the field_attach.api but I cant find any examples that go through the whole create field -> attach field to something. Cheers love the site :-)

My bad I see the follow-up blog has the answer :-)

Unpublished

I see the answer is here in this post.
http://public-action.org/content/drupal-7-field-api-drupal-7-adding-cust...
However how would you add it to say

user/%/edit page?

Change the entity type to user

If you takes the code in http://public-action.org/content/drupal-7-field-api-drupal-7-adding-cust... and change the entity type to user, wouldn't that do it?

In Drupal 7 both Nodes and User objects are specializations of Entity. Thus replacing an entity type of "node" with an entity type of "user" should suffice.