Custom Form API Elements from Custom Fields

Some time back, I wrote a blog post entitled Compound fields in Drupal 7 where I discussed how to create custom fields for your content types using Field API. Since then, I've often been asked how to create custom fields for use in forms (using Form API), outside of using those fields in entities such as nodes and taxonomy terms.

Assuming you've read that blog post, I'll now show you how to extend an existing custom field into a form element that you can use in any FAPI form.

Tobby Hagler, Director of Engineering
#API | Posted

Some time back, I wrote a blog post entitled Compound fields in Drupal 7 where I discussed how to create custom fields for your content types using Field API. Since then, I've often been asked how to create custom fields for use in forms (using Form API), outside of using those fields in entities such as nodes and taxonomy terms.

Assuming you've read that blog post, I'll now show you how to extend an existing custom field into a form element that you can use in any FAPI form.

Hooks

To extend your field, you'll need to add some additional hooks to your module. It's important to note that you can extend any field into a form element that exists in Drupal, not just something defined by your module.

hook_element_info -- This hook tells Drupal about your form element.

  1. 1 function dnd_fields_element_info() {
  2. 2  return array(
  3. 3    'dnd_fields_attribute' => array(
  4. 4      '#input' => TRUE,
  5. 5      '#tree' => TRUE,
  6. 6      '#process' => array('dnd_fields_attribute_process'),
  7. 7      '#element_validate' => array('dnd_fields_element_validate'),
  8. 8      '#theme' => array('dnd_fields_element'),
  9. 9      '#theme_wrappers' => array('form_element'),
  10. 10    ),
  11. 11  );
  12. 12 }

Process Functions

In hook_element_info, we named a processor function that will define how our new form element should be processed.

In this example, we are "faking" a field instance so that we can reuse the field widget defined in hook_field_widget_form (dnd_fields_field_widget_form). If you prefer, you can skip to the next code sample to create a standalone element processor function without relying on what requirements this Field API hook has.

  1. function dnd_fields_attribute_process($element, &$form_state) {
  2. // Create a dummy field instance just to get the same output from our existing field widget
  3. $instance = array(
  4. 'field_name' => 'dnd_fields_ability',
  5. 'settings' => array(),
  6. 'widget' => array(
  7. 'type' => 'dnd_fields_ability',
  8. ),
  9. );
  10.  
  11. $form = array();
  12. $field = array(
  13. 'settings' => array(
  14. 'abilities' => array(),
  15. ),
  16. );
  17. $langcode = LANGUAGE_NONE;
  18. $items = array();
  19. $delta = 0;
  20. $form_state['field'] = array(
  21. 'dnd_fields_ability' => array(
  22. $langcode => array(
  23. 'field' => array(
  24. 'type' => 'dnd_fields_ability',
  25. 'cardinality' => 6,
  26. 'settings' => array(),
  27. ),
  28. ),
  29. ),
  30. );
  31.  
  32. $element = dnd_fields_field_widget_form($form, $form_state, $field, $instance, $langcode, $items, $delta, $element);
  33. return $element;
  34. }

If you are creating a new form element that does not come from an existing field, you will want this instead:

  1. function dnd_fields_attribute_process($element, &$form_state) {
  2. $fields = array(
  3. 'ability' => t('Ability'),
  4. 'score' => t('Score'),
  5. 'mod' => t('Modifier'),
  6. 'tempscore' => t('Temp score'),
  7. 'tempmod' => t('Temp modifier'),
  8. );
  9.  
  10. foreach ($fields as $key => $label) {
  11. $element[$key] = array(
  12. '#attributes' => array('class' => array('edit-dnd-fields-ability'), 'title' => t('')),
  13. '#type' => 'textfield',
  14. '#size' => 3,
  15. '#maxlength' => 3,
  16. '#title' => $label,
  17. '#default_value' => NULL,
  18. '#attached' => array(
  19. 'css' => array(drupal_get_path('module', 'dnd_fields') . '/dnd_fields.3x.css'),
  20. 'js' => array(drupal_get_path('module', 'dnd_fields') . '/dnd_fields.3x.js'),
  21. ),
  22. '#prefix' => '<div class="dnd-fields-ability-field dnd-fields-ability-' . $key . '-field">',
  23. '#suffix' => '</div>',
  24. );
  25. }
  26.  
  27. return $element;
  28. }

Technically, we're done. You now have a form element that you can define in any Form API code. For example:

  1. function dnd_character_test_form($form, &$form_state) {
  2. $form = array();
  3. $form['test'] = array(
  4. '#type' => 'dnd_fields_attribute',
  5. '#title' => t('Attribute fields'),
  6. '#description' => t('Provide a description here.'),
  7. );
  8. $form['submit'] = array(
  9. '#type' => 'submit',
  10. '#value' => t('Submit'),
  11. );
  12. return $form;
  13. }

Themeing your Element

The cool thing with your new form element is that you can theme it, even though this is not strictly neccessary, since the widget defined in the field will handle most of this for you. So feel free to skip this step if you're already happy with the Field API widget that you have.

The following code sample displays a generic form output, since the field that we defined is really just a special case of the 'textfield' form element type. However, you can feel free to get fancy here, and display literally any HTML output you want.

Also of note is that this is just like any other theme function in Drupal. You can refine this more using preprocess theme functions and .tpl.php files to theme your form element even more. However, for this example, I'm going to keep it simple.

  1. function theme_dnd_fields_element($variables) {
  2. $element = $variables['element'];
  3.  
  4. $attributes = array();
  5. if (isset($element['#id'])) {
  6. $attributes['id'] = $element['#id'];
  7. }
  8. if (!empty($element['#attributes']['class'])) {
  9. $attributes['class'] = (array) $element['#attributes']['class'];
  10. }
  11. $attributes['class'][] = 'dnd-fields-ability';
  12.  
  13. // This wrapper is required to apply JS behaviors and CSS styling.
  14. $output = '';
  15. $output .= '<div' . drupal_attributes($attributes) . '>';
  16. $output .= drupal_render_children($element);
  17. $output .= '</div>';
  18. return $output;

Tobby Hagler

Director of Engineering