Altering Individual Radio or Checkbox Items in Drupal 7 via FAPI

This guide will focus on the radios type, but it can be use for checkboxes in the exact same way.

TL;DR: Put an #after_build on form checkboxes or radios (or on the form element itself). Within the after_build, the items will be normal radio or checkbox renderable elements.

When using FAPI with #type => 'radios', it becomes quickly obvious that defining or altering the individual radio items is difficult, as all options go into the nested #options property:


$form['example_radios'] = array( '#type' => 'radios', '#title' => t('Example Radios Element'), '#options' => array( 'key1' => t('Value1'), 'key2' => t('Value1'), 'key3' => t('Value1'), ), );

We can see the cause of this in form_process_radios, where for each item in #options is given some defaults but doesn't allow for any custom settings. If we need more control, there are two options.

Option 1: Adding HTML to the Label

First off, if you're just looking to add some HTML as the label, you can do so directly. Drupal allows the value of an item in #options to contain HTML. We can see where this happens in the previous link, as it doesn't run through any text processing: '#title' => $choice.

Therefore, you can do a normal hook_form_[_FORM_ID_]alter and replace $form['example_radios']['#options']['key1'] with HTML either directly or using render($drupal_render_array);.

Option 2: Full Control Using #after_build

However, advanced manipulation of a radio element and it's properties require a little extra work via FAPI's #after_build feature. This includes any common properties such as #prefix, #suffix, #attributes, etc.

If you are unfamiliar with #after_build, it's a relatively simple part of the form processing process. Without going into detail, you can think of it simply as a similar idea to form building and altering. The difference being is that it runs after any other module's form processing and altering. Further, similar to other functionality such as #validate, this property can be set on the element or form level.

If you're interested in learning more about the FAPI process and particularly #after_build, there's already lots of good resources out there.

Fortunately, this is not a particularly difficult task, as we only need to add the property to the array, such as $form['example_radios']['#after_build'][] = 'example_radios_after_build';, where example_radios_after_build is the callback function. Next, this callback needs to be created:

function example_radios_after_build($element, &$form_state) {

  // Always return the element to render in after_build callbacks.
  return $element;
}

If you do a dpm on the element, you will see that each radio element now has it's own renderable FAPI array, keyed using the keys in the definition. Therefore, to iterate over each individual radio element, we use:

function example_radios_after_build($element, &$form_state) {

  // Each renderable radio element.
  foreach (element_children($element) as $key) {

  }

  // Always return the element to render in after_build callbacks.
  return $element;

}

Printing the individual elements via dpm($element[$key]) in the iteration above should look familiar. Each item will now be a standard FAPI item where #type => 'radio'.

This results in some interesting usability and styling options for the radio items.

Example Time

For example, suppose we had the core Field UI module enabled as well as Entity Reference, and created an entity reference to a node called field_somefield_reference with the radios widget type on the user_profile form (IE. attached t. Then, on the form itself, we wanted to display a rendering of the node referenced by the field:


/** * Implements hook_entity_info_alter(). */ function MYMODULE_entity_info_alter(&$entity_info) { // Add a registration view mode for nodes. $entity_info['node']['view modes']['registration'] = array( 'label' => t('Registration'), 'custom settings' => TRUE, ); } /** * Implements hook_form_FORM_ID_alter(). * * The user registration form. */ function MYMODULE_form_user_register_form_alter(&$form, &$form_state) { // Add an after build to somefield_reference. $form['field_somefield']['#after_build'][] = 'MYMODULE_form_user_register_form_field_somefield_reference'; } /** * After build callback for field_somefield_reference on user registration forms. */ function MYMODULE_form_user_register_form_field_somefield_reference($element, &$form_state) { // Each renderable radio element. foreach (element_children($element) as $nid) { // Pull the original form item. $field_somefield_item = $element[$nid]; // Load the node. $node = node_load($nid); // View the package using our custom view mode. $view = node_view($node, 'registration'); // Update the radio item so the button shows then the rendered node. $element[$nid] = array( // Wrap the new item for styling. '#prefix' => '<div class="node-form-radio-with-render">', '#suffix' => '</div>', // Make sure to use the initial key so FAPI saves the values correctly. $nid => $field_somefield_item, // Add the renderable node. 'node_view' => $view, ); } // Always return the element to render in after_build callbacks. return $element; }

If you are unfamiliar with view modes, the first function above will add a custom view mode to node entity types. Afterwards we can navigate to admin/structure/types/manage/NODE_TYPE/display/registration and add the fields we want to see. Alternatively, we could just use one of the built-in types (full or teaser).

Now when we view this form, each radio will have a rendered entity beside it's button.

Here's an example of an entity reference element with the radios widget using after_build to style the entities: