by Karen Stevenson on July 15, 2009 // Short URL

Creating Custom CCK Fields

You can create custom CCK fields, widgets, and formatters for any situation, but it can be hard to see how to do it. I finally found time to create an 'Example' module that creates a simple field, formatter, and widget, with lots of embedded documentation about what belongs where. You need to create three files, an .info file, an .install file, and the module itself. The code below creates a very simple textfield, but it can be used as a starting point for any custom module. I'm also attaching a .zip file with the contents of this custom module.

The .info File

; $Id$
name = Example field
description = Defines an example field type.
dependencies[] = content
package = CCK
core = 6.x

The .install File

<?php
// $Id$
// Notify CCK when this module is enabled, disabled, installed,
// and uninstalled so CCK can do any necessary preparation or cleanup.
/**
* @file
* Implementation of hook_install().
*/
function example_install() {
 
drupal_load('module', 'content');
 
content_notify('install', 'example');
}

/**
* Implementation of hook_uninstall().
*/
function example_uninstall() {
 
drupal_load('module', 'content');
 
content_notify('uninstall', 'example');
}

/**
* Implementation of hook_enable().
*
* Notify content module when this module is enabled.
*/
function example_enable() {
 
drupal_load('module', 'content');
 
content_notify('enable', 'example');
}

/**
* Implementation of hook_disable().
*
* Notify content module when this module is disabled.
*/
function example_disable() {
 
drupal_load('module', 'content');
 
content_notify('disable', 'example');
}
?>

The Module

<?php
// $Id$

/**
* @file
* An example to define a simple field, widget, and formatter.
* A module could define only a field, only a widget, only a
* formatter, or any combination. Widgets and formatters must
* declare what kind of field they work with, which can be any
* existing field as well as any new field the module creates.
*/

//==========================================//
// DEFINING A FIELD
//==========================================//

/**
* Implementation of hook_field_info().
*/
function example_field_info() {
  return array(
   
// The machine name of the field,
    // no more than 32 characters.
   
'example' => array(
     
// The human-readable label of the field that will be
      // seen in the Manage fields screen.
     
'label' => t('Example field'),
     
// A description of what type of data the field stores.
     
'description' => t('Store text data in the database.'),
     
// An icon to use in Panels.
     
'content_icon' => 'icon_content_text.png',
    ),
  );
}

/**
* Implementation of hook_field_settings().
*/
function example_field_settings($op, $field) {
  switch (
$op) {
   
// Create the form element to be used on the field
    // settings form. Field settings will be the same for
    // all shared instances of the same field and should
    // define the way the value will be stored
    // in the database.
   
case 'form':
     
$form = array();
     
$form['max_length'] = array(
       
'#type' => 'textfield',
       
'#title' => t('Maximum length'),
       
'#default_value' => is_numeric($field['max_length']) ? $field['max_length'] : 255,
       
'#required' => FALSE,
       
       
// Use #element_validate to validate the settings.
       
'#element_validate' => array('_example_length_validate'),
       
'#description' => t('The maximum length of the field in characters. Must be a number between 1 and 255'),
      );
      return
$form;
     
   
// Return an array of the names of the field settings
    // defined by this module. These are the items that
    // CCK will store in the field definition
    // and they will be available in the $field array.
    // This should match the items defined in 'form' above.
   
case 'save':
      return array(
'max_length');

   
// Define the database storage for this field using
    // the same construct used by schema API. Most fields
    // have only one column, but there can be any number
    // of different columns. After the schema API values,
    // add two optional values to each column,
    //  'views', to define a Views field
    //  'sortable', to add a Views sort field
   
case 'database columns':
     
$columns['value'] = array(
       
'type' => 'varchar',
       
'length' => is_numeric($field['max_length']) ? $field['max_length'] : 255,
       
'not null' => FALSE,
       
'sortable' => TRUE,
       
'views' => TRUE,
      );
      return
$columns;

   
// Optional: Make changes to the default $data array
    // created for Views. Omit this if no changes are
    // needed, use it to add a custom handler or make
    // other changes.
   
case 'views data':
     
// Start with the $data created by CCK
      // and alter it as needed. The following
      // code illustrates how you would retrieve
      // the necessary data.
     
$data = content_views_field_views_data($field);
     
$db_info = content_database_info($field);
     
$table_alias = content_views_tablename($field);
     
$field_data = $data[$table_alias][$field['field_name'] .'_value'];
     
// Make changes to $data as needed here.
     
return $data;
  }
}

/**
* Custom validation of settings values.
*
* Create callbacks like this to do settings validation.
*/
function _example_length_validate($element, &$form_state) {
 
$value = $form_state['values']['max_length'];
  if (
$value && !is_numeric($value)|| $value < 1 || $value > 255) {
   
form_set_error('max_length', t('"Max length" must be a number between 1 and 255.'));
  }
}

/**
* Implementation of hook_field().
*/
function example_field($op, &$node, $field, &$items, $teaser, $page) {
  switch (
$op) {
   
// Do validation on the field values here. The widget
    // will do its own validation and you cannot make any
    // assumptions about what kind of widget has been used,
    // so don't validate widget values, only field values.
   
case 'validate':
      if (
is_array($items)) {
        foreach (
$items as $delta => $item) {
         
// The error_element is needed so that CCK can
          // set an error on the right sub-element when
          // fields are deeply nested in the form.
         
$error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
          if (
is_array($item) && isset($item['_error_element'])) unset($item['_error_element']);
          if (!empty(
$item['value'])) {
            if (!empty(
$field['max_length']) && drupal_strlen($item['value']) > $field['max_length']) {
             
form_set_error($error_element, t('%name: the value may not be longer than %max characters.', array('%name' => $field['widget']['label'], '%max' => $field['max_length'])));
            }
          }
        }
      }
      return
$items;

   
// This is where you make sure that user-provided
    // data is sanitized before being displayed.
   
case 'sanitize':
      foreach (
$items as $delta => $item) {
       
$example = check_plain($item['value']);
       
$items[$delta]['safe'] = $example;
      }
  }
}

/**
* Implementation of hook_content_is_empty().
*
* CCK has no way to know if something like a zero is
* an empty value or a valid value, so return
* TRUE or FALSE to a populated field $item array.
* CCK uses this to remove empty multi-value elements
* from forms.
*/
function example_content_is_empty($item, $field) {
  if (empty(
$item['value'])) {
    return
TRUE;
  }
  return
FALSE;
}

/**
* Implementation of hook content_generate().
*
* Optional, provide dummy value for nodes created
* by the Devel Generate module.
*/
function example_content_generate($node, $field) {
 
$node_field = array();
 
// Generate a value that respects max_length.
 
if (empty($field['max_length'])) {
   
$field['max_length'] = 12;
  }
 
$node_field['value'] = user_password($field['max_length']);
  return
$node_field;
}

/**
* Implementation of hook_token_list()
* and hook_token_values().
*
* Optional, provide token values for this field.
*/
function example_token_list($type = 'all') {
  if (
$type == 'field' || $type == 'all') {
   
$tokens = array();

   
$tokens['example']['raw']       = t('Raw, unfiltered text');
   
$tokens['example']['formatted'] = t('Formatted and filtered text');

    return
$tokens;
  }
}

function
example_token_values($type, $object = NULL) {
  if (
$type == 'field') {
   
$item = $object[0];

   
$tokens['raw']  = $item['value'];
   
$tokens['formatted'] = isset($item['view']) ? $item['view'] : '';
    return
$tokens;
  }
}

//==========================================//
// DEFINING A FORMATTER
//==========================================//

/**
* Implementation of hook_theme().
*/
function example_theme() {
  return array(
   
// Themes for the formatters.
   
'example_formatter_default' => array(
     
'arguments' => array('element' => NULL),
    ),
   
'example_formatter_plain' => array(
     
'arguments' => array('element' => NULL),
    ),
  );
}

/**
* Implementation of hook_field_formatter_info().
*
* All fields should have a 'default' formatter.
* Any number of other formatters can be defined as well.
* It's nice for there always to be a 'plain' option
* for the raw value, but that is not required.
*
*/
function example_field_formatter_info() {
  return array(
   
// The machine name of the formatter.
   
'default' => array(
     
// The human-readable label shown on the Display
      // fields screen.
     
'label' => t('Default'),
     
// An array of the field types this formatter
      // can be used on.
     
'field types' => array('example'),
     
// CONTENT_HANDLE_CORE:   CCK will pass the formatter
      // a single value.
      // CONTENT_HANDLE_MODULE: CCK will pass the formatter
      // an array of all the values. None of CCK's core
      // formatters use multiple values, that is an option
      // available to other modules that want it.
     
'multiple values' => CONTENT_HANDLE_CORE,
    ),
   
'plain' => array(
     
'label' => t('Plain text'),
     
'field types' => array('example'),
     
'multiple values' => CONTENT_HANDLE_CORE,
    ),
  );
}

/**
* Theme function for 'default' example field formatter.
*
* $element['#item']: the sanitized $delta value for the item,
* $element['#field_name']: the field name,
* $element['#type_name']: the $node->type,
* $element['#formatter']: the $formatter_name,
* $element'#node']: the $node,
* $element['#delta']: the delta of this item, like '0',
*
*/
function theme_example_formatter_default($element) {
  return
$element['#item']['safe'];
}

/**
* Theme function for 'plain' example field formatter.
*/
function theme_example_formatter_plain($element) {
  return
strip_tags($element['#item']['safe']);
}

//==========================================//
// DEFINING A WIDGET
//==========================================//

/**
* Implementation of hook_widget_info().
*
* Here we indicate that the content module will handle
* the default value and multiple values for these widgets.
*
* Callbacks can be omitted if default handing is used.
* They're included here just so this module can be used
* as an example for custom modules that might do things
* differently.
*/
function example_widget_info() {
  return array(
   
// The machine name of the widget, no more than 32
    // characters.
   
'example_widget' => array(
     
// The human-readable label of the field that will be
      // seen in the Manage fields screen.
     
'label' => t('Example widget'),
     
// An array of the field types this widget can be
      // used with.
     
'field types' => array('example'),
     
// Who will handle multiple values, default is core.
      // 'CONTENT_HANDLE_MODULE' means the module does it.
      // See optionwidgets for an example of a module that
      // handles its own multiple values.
     
'multiple values' => CONTENT_HANDLE_CORE,
     
'callbacks' => array(
       
// Who will create the default value, default is core.
        // 'CONTENT_CALLBACK_CUSTOM' means the module does it.
        // 'CONTENT_CALLBACK_NONE' means this widget has
        // no default value.
       
'default value' => CONTENT_CALLBACK_DEFAULT,
      ),
    ),
  );
}

/**
* Implementation of hook_widget_settings().
*/
function example_widget_settings($op, $widget) {
  switch (
$op) {
   
// Create the form element to be used on the widget
    // settings form. Widget settings can be different
    // for each shared instance of the same field and
    // should define the way the value is displayed to
    // the user in the edit form for that content type.
   
case 'form':
     
$form = array();
     
$size = (isset($widget['size']) && is_numeric($widget['size'])) ? $widget['size'] : 60;
     
$form['size'] = array(
       
'#type' => 'textfield',
       
'#title' => t('Size of textfield'),
       
'#default_value' => $size,
       
'#element_validate' => array('_element_validate_integer_positive'),
       
'#required' => TRUE,
      );
      return
$form;

   
// Return an array of the names of the widget settings
    // defined by this module. These are the items that
    // CCK will store in the widget definition and they
    // will be available in the $field['widget'] array.
    // This should match the items defined in 'form' above.
   
case 'save':
      return array(
'size');
  }
}

/**
* Implementation of hook_widget().
*
* Attach a single form element to the form.
*
* CCK core fields only add a stub element and builds
* the complete item in #process so reusable elements
* created by hook_elements can be plugged into any
* module that provides valid $field information.
*
* Custom widgets that don't care about using hook_elements
* can be built out completely at this time.
*
* If there are multiple values for this field and CCK is
* handling multiple values, the content module will call
* this function as many times as needed.
*
* @param $form
*   the entire form array,
*   $form['#node'] holds node information
* @param $form_state
*   the form_state,
*   $form_state['values'][$field['field_name']]
*   holds the field's form values.
* @param $field
*   the field array
* @param $items
*   array of default values for this field
* @param $delta
*   the order of this item in the array of
*   subelements (0, 1, 2, etc)
*
* @return
*   the form item for a single element for this field
*/
function example_widget(&$form, &$form_state, $field, $items, $delta = 0) {
 
 
$element['value'] = array(
   
'#type' => 'textfield',
   
'#default_value' => isset($items[$delta]['value']) ? $items[$delta]['value'] : NULL,
   
'#autocomplete_path' => $element['#autocomplete_path'],
   
'#size' => !empty($field['widget']['size']) ? $field['widget']['size'] : 60,
   
'#attributes' => array('class' => 'example'),
   
'#maxlength' => !empty($field['max_length']) ? $field['max_length'] : NULL,
  );
 
 
// Used so that hook_field('validate') knows where to
  // flag an error in deeply nested forms.
 
if (empty($form['#parents'])) {
   
$form['#parents'] = array();
  }
 
$element['_error_element'] = array(
   
'#type' => 'value',
   
'#value' => implode('][', array_merge($form['#parents'], array('value'))),
  );
 
  return
$element;
}
?>

Karen Stevenson

Senior Drupal Architect

Want Karen Stevenson to speak at your event? Contact us with the details and we’ll be in touch soon.

Comments

TUc

It seems at odds with the

It seems at odds with the popularity of CCK, but this kind of information is rare. Thanks for taking the time to write this post, Karen.

Reply

volkan

I might be a little

I might be a little ill-informed, but this confuses me. Can't you create custom input fields using CCK already? Why create a new module to do that?

Secondly, all this terminology about widgets is also kind of confusing. I sense that this word has a specific meaning in CCK that I am not aware of. CCK has a lot of terms that don't mean what I think they mean. Is there a glossary for these terms?

Reply

Anthony Hersey

The modules described here

The modules described here would be for adding new field types into the UI. If you peek at the code for the CCK textfield module, you'll see those same functions in use.

Reply

ebranda

Thanks for this. It is

Thanks for this. It is surprisingly hard to find a clear explanation, and there are many ways to break it so it is very helpful.

Reply

ebranda

Existing field list?

One minor problem: I added a new instance of the custom field to a node type. When I went back to the node type "Manage Fields" form the custom field did not appear in the "Select an existing field" select list in the "Add" section. Any idea how to do this? Thanks again...

Reply

Kelly

Thank you

Thank you for this. I want to tie this in with the ubercart module for my Handmade Bags site. Do you know if I need to do anything special to make this work with that module? I want to be able to create some custom fields related to fabric types, sizes, sales, etc. Thanks for posting such a simple example.

Reply

mathieso

(Soulful look): Please, ma'am, may we have more?

Very useful post! I have just about every book and video (including Lullabot's, of course) on Drupal development, and still have much to learn. Articles like this help a great deal.

Thnx!

Kieran

Reply

Anonymous

RE: I might be a little & Thank you

This example details creating entirely new classes of field types (for example, the date module needs to create a field type for dates, and cannot use existing 'text' field type). We will be using this example to create geospatial fields to store mapping data. For most uses (for example creating sizes, fabric types etc) CCK built-in text and number field types should work just fine. This example shows you how to build new classes of fields if the existing types are not working for you.

Great little tutorial!

Reply

arpieb

Label issues...?

I just installed this example, enabled the module, and defined a new content type using the example field. When I go to create new content with my new type, the text input field is there, but no label gets generated (not even in the raw XHTML code). Am I missing something?

I'm running D6.13 on Debian Linux, CCK 6.x-2.4 installed.

Other than that - thanks for the great example and starting point!

Reply

Anonymous

That's because there is no

That's because there is no ['#title'] property set in the widget form. You need to set it yourself. Here is an example:

<?php
$element
['value'] = array(
   
'#type' => 'textfield',
   
'#title' => check_plain($field['widget']['label']),
   
'#default_value' => isset($items[$delta]['value']) ? $items[$delta]['value'] : NULL,
   
'#size' => !empty($field['widget']['size']) ? $field['widget']['size'] : 60,
  );
?>
Reply

arpieb

Thanks! That definitely

Thanks! That definitely fixed it.

Some better documentation on what exactly is being passed into these hooks and what a field module developer needs to tap from those parameters would sure be nice, especially for someone on a tight timeline who doesn't have the time to dig through a.d.o and the source code in an attempt to resolve every single issue you run into...

Reply

posco

Install devel module, kpr() is your best friend

I highly recommend installing the devel module and using the kpr() function, which uses Krumo (a fancy CSS UI) to print PHP arrays and objects to your web-browser. So, to examine what is available to you in hook_widget(), you can do this:

<?php
if (module_exists('devel')) {
 
kpr($field);
 
kpr($items);
 
kpr($form);
 
kpr($form_state);
}
?>

Add your field to a content type, go to Create content > Your Content Type and browse the Krumo printouts at the top. You can even do something fancier like this:

<?php
if (isset($_GET['debug_toggle']) && module_exists('devel')) {
 
kpr($field);
 
kpr($items);
 
kpr($form);
 
kpr($form_state);
}
?>

And that will let you toggle the Krumo printouts using a debug_toggle flag in your URL. So say you had your field on the Story content-type, you would do something like this:

http://your-domain/node/add/story?debug_toggle=1

and you would see your printouts. Going to the URL normally would hide them.:

http://your-domain/node/add/story

That's nice for figuring stuff out in the short-term. IMO, you should take statements like that out before production deployment, but you can also use roles to determine who can and who can't see kpr() printouts, the permission being 'access devel information'.

Reply

rohit sabikhi

Need custom field for firstname Lastname salutaion[drop down]

Hi,

I need a custom field which comprises of three fields
1. A drop down to choose your salutation [ eg. Mr, Mrs, Dr etc]
2. text box for firstname
3. text box for last name

I tried following this tutorial to get the field working but things didn't work out well.
The module gets loaded, all the database entries are made perfectly.
When I add this field to my node it gets added but then i get a blank page on relading the content type page I get the configuration.

Then when I go to a create new content of the above content type. The display oin this custom field is blank, and i dont see and text box to add data into.

Please suggest what the problem could be.
Also please provide an example that takes more than one textboxes/ fields.

Reply

Jon

Hierarchies with multiple field values?

Hello all,

From a UX perspective, I am looking for precisely the same experience you get from having an unlimited multiple-value, text-field-based, Text field, with the addition that it allows for hierarchy to manipulate the delta. I assume if I created my own module that I would need to add a column to the schema to have items refer to their parents, but don't know how to incorporate the theming (or is it FAPI?) aspect.

I used the example provided in this fantastic blog post, and successfully added the "parent item" field, but wonder exactly how to incorporate drupal_add_tabledrag() into the module.

I saw in example_widget_info() that you could set 'multiple values' to CONTENT_HANDLE_MODULE. I checked out the optionwidgets module, but did not understand exactly what hooks to look for.

Any hints or pointers? Thank you!

Reply

Vlad Rosca

Thank You!

Hi,

I just want to thank Lullabot and Karen for this great tutorial. It saved me a lot of time - and pain :).

Reply

Marco

THANKS!

Thank you very much Karen!! For the explanation and for the working module, exactly what I need to start. Thanks again

Reply

Anonymous

Compound CCK fields, can I?

You said "A module could define only a field, only a widget, only a formatter, or any combination"

I want to create a compound cck field, with a few textfield, one text area, one check boxes, and one image field field. Can I?

Thanks,

Reply

Do Ngoc Tu

How hightlight when element is be error?

now, if you setting with maxlength =10
then you create content, you enter with maxlength >10 to element value.
i sure that it do'nt highlight point to element which error.

To hightlight, i implement replace code

function example_widget(&$form, &$form_state, $field, $items, $delta = 0) {
 
  $element['value'] = array(
    '#type' => 'textfield',
    '#default_value' => isset($items[$delta]['value']) ? $items[$delta]['value'] : NULL,
    '#autocomplete_path' => $element['#autocomplete_path'],
    '#size' => !empty($field['widget']['size']) ? $field['widget']['size'] : 60,
    '#attributes' => array('class' => 'example'),
   
  );
 
  // Used so that hook_field('validate') knows where to
  // flag an error in deeply nested forms.
  if (empty($form['#parents'])) {
    $form['#parents'] = array();
  }
  $pre=array();
  $pre=array_merge(array($field['field_name']),array($delta));
  $element['_error_element'] = array(
    '#type' => 'value',
    '#value' => implode('][',array_merge($pre,array('value'))),
  );

  return $element;
}
Reply

alex

Hi, I am trying to create a

Hi,

I am trying to create a content type as above, so far so good, I have added a few text fields and they are being recorded. I am now interested in using the filefield/imagefield to add the possibility of using pictures on content type.

i.e:
| species text field | quantity text field | image field |

I'm using the poplar example as well, but I keep on getting errors. Would you be able to give me pointers on how to add the above field?

Many thanks in advance,

Alex

Reply