Drupal 7 Form API select element option attributes

In the topic: How to add any kind of attribute (class, disabled, data attributes, etc…) into a Drupal Form API form.

A while ago I had a task on creating some jQuery magic regarding select options. Basically I needed to add extra data attributes to select option elements, and access these options via JavaScript when a user clicks the option.

I thought that it would be a walk in the park. The form in question was already from our custom module, created via the Drupal Form API. So this should be easy, right?

Right?

Well…

After a frustrating 20 minutes, I found out that this couldn’t be currently accomplished. The Form API doesn’t have functionality to add anything else than the key and the value to the select options… At least, not without some theme function override wizardry.

In Drupal, many theme functions can be overridden by a custom theme’s own implementation. In this case we needed to overwrite the basic theme_select function with our own, and make that function call our own function that will create the option elements.

While working on implementing this, I soon realized that this could be used for much more than just adding data attributes to the options. For example, custom classes per option, and disabled individual options in a select element. The disabled individual elements have come handy on quite a lot of sites.

Here is an example of a select element with some custom classes, a disabled attribute and a data attribute:

<?php

/**
 * The #option_attributes array has the same keys as the #options array
 * This is how we link the right attributes to the option on the element
 * creation.
 *
 * The option attributes value is an array, with the key acting as the
 * attribute name and the value as the attribute value.
 * Example:
 * array(
 *  'disabled' => 'true',
 *  'class' => 'disabled',
 *  'data-nid' => '20'
 * )
 * translates to:
 * <option disabled='true' class='disabled' data-nid='20'>
 */
$form['exove_select'] = array(
  '#type' => 'select',
  '#title' => t('Example select with attributes'),
  '#options' => array(
    0 => 'You cannot select this'
    1 => 'One',
    2 => 'Two',
    3 => 'Three',
    'all' => 'With a data attribute',
  ),
  '#option_attributes' = array(
    0 => array(
      'class' => 'zero',
      'disabled' => 'true',
    ),
    1 => array(
      'class' => 'one',
    ),
    2 => array(
      'class' => 'two',
    ),
    3 => array(
      'class' => 'three',
    ),
    'all' => array(
      'class' => 'all',
      'data-allselect' => 'true'
    ),
  ),
);

Here is the theme code, it belongs in your theme’s template.php file:

<?php

/**
 * We override the normal theme_select function that takes care
 * of theming the select element.
 * Change THEME to your theme name.
 */
function THEME_select($variables) {
  $element = $variables['element'];
  element_set_attributes($element, array('id', 'name', 'size'));
  _form_set_class($element, array('form-select'));

  // Now we implement our own custom function for theming the options.
  // Change THEME to your theme name.
  return '<select' . drupal_attributes($element['#attributes']) . '>'
    . THEME_form_select_options($element) . '</select>';
}

/**
 * This is a copy of normal form select options,
 * with option attribute support.
 * Change THEME to your theme name.
 */
function THEME_form_select_options($element, $choices = NULL) {
  if (!isset($choices)) {
    $choices = $element['#options'];
  }
  // array_key_exists() accommodates the rare event
  // where $element['#value'] is NULL.
  // isset() fails in this situation.
  $value_valid = isset($element['#value'])
    || array_key_exists('#value', $element);
  $value_is_array = $value_valid && is_array($element['#value']);
  $options = '';
  foreach ($choices as $key => $choice) {
    if (is_array($choice)) {
      $options .= '<optgroup label="' . $key . '">';
      $options .= form_select_options($element, $choice);
      $options .= '</optgroup>';
    }
    elseif (is_object($choice)) {
      $options .= form_select_options($element, $choice->option);
    }
    else {
      $key = (string) $key;

      // Get option attributes if set.
      // The attributes are added to the parent select element,
      // which we can access here ($element)
      // The #option_attributes have the same keys as the #option
      // elements, and can be added here to the current option by key
      $attributes = "";
      if (isset($element['#option_attributes'][$key])) {
        $attributes = drupal_attributes($element['#option_attributes'][$key]);
      }

      // check if this element is selected
      if ($value_valid
        && (!$value_is_array && (string) $element['#value'] === $key
        || ($value_is_array && in_array($key, $element['#value'])))) {
        $selected = ' selected="selected"';
      } else {
        $selected = '';
      }

      // Here we add the attributes to the option
      $options .= '<option ' . $attributes
        . ' value="' . check_plain($key) . '"' . $selected . '>'
        . check_plain($choice) . '</option>';
    }
  }
  return $options;
}

Further reading

Drupal.org

Thoughts by

Hiski Valli - Exove

Hiski Valli

Senior Developer

02.03.2015

Share on social media:

Latest thoughts