'options', * 'limit' => 20, * 'optgroups' => FALSE, * 'multiple' => FALSE, * 'options' => array( * 'foo' => 'foo', * 'bar' => 'bar', * 'baz' => 'baz', * ), * 'default_value' => 'foo' * 'key_type' => 'associative', * ); * @endcode * * Properties for the 'options' element include: * - limit: The maximum number of options that can be added to a list. Defaults * to 100. * - optgroups: If nesting of options is supported, up to one level. This is * used when building a select HTML element that uses optgroups. Defaults to * FALSE. * - multiple: Affects the number of default values that may be selected. * - default_value: The key(s) for the options that are currently selected. If * #multiple is TRUE then, the default value is an array, otherwise it is a * string. * - options: An array of options currently within the list. * - key_type: The method by which keys are determined for each value in the * option list. Available options include: * - numeric: Each value is automatically given a unique numeric ID. This can * be useful when wanting duplicate values in a list and not have to bother * the end-user for keys. * - associative: Keys are automatically mapped from the user-entered values. * This is equivalent to making key|value pairs, but both the key and value * are the same. Each key must be unique. * - custom: Keys are manually entered by the end user. A second set of * textfields are presented to let the user provide keys as well as values. * - none: No keys are specified at all. This effectively creates numeric keys * but unlike numeric keys, the keys are renumbered if the options in the * list are rearranged. * - key_type_toggle: If specified, a checkbox will be added that allows the * user to toggle between the current "associative" key type and the "custom" * key type, letting them customize the keys as desired. This option has no * effect with "numeric" or "none" key types. * @code * $element['options'] = array( * 'type' => 'options', * 'key_type' => 'associative', * 'key_type_toggle' => t('Custom keys'), * ); * @endcode */ function options_element_elements() { $type = array(); $type['options'] = array( '#input' => TRUE, '#process' => array('form_options_expand'), '#limit' => 100, '#optgroups' => TRUE, '#options' => array(), '#key_type' => 'associative', ); return $type; } /** * Implementation of hook_theme(). */ function options_element_theme() { return array( 'options' => array( 'arguments' => array('element' => NULL), ), ); } /** * Expand the "options" form element type. * * The "options" type is simply an enhanced textarea that makes it easier to * create key|value pairs and put items into optgroups. */ function form_options_expand($element) { $element['#options'] = isset($element['#options']) ? $element['#options'] : array(); $element['#multiple'] = isset($element['#multiple']) ? $element['#multiple'] : FALSE; $element['#tree'] = TRUE; $element['#theme'] = 'options'; // Add the key type toggle checkbox. if (!isset($element['key_type']) && !empty($element['#key_type_toggle'])) { $element['key_type'] = array( '#title' => is_string($element['#key_type_toggle']) ? $element['#key_type_toggle'] : t('Customize keys'), '#type' => 'checkbox', '#return_value' => 'custom', '#default_value' => $element['#key_type'] == 'custom' ? 'custom' : '', '#attributes' => array('class' => 'key-type-toggle'), '#description' => t('Customizing the keys will allow you to save one value internally while showing a different option to the user. Specifying keys manually will let you change the values later without loosing data.'), ); } // Add the multiple value toggle checkbox. if (!isset($element['multiple']) && !empty($element['#multiple_toggle'])) { $element['multiple'] = array( '#title' => is_string($element['#multiple_toggle']) ? $element['#multiple_toggle'] : t('Allow multiple values'), '#type' => 'checkbox', '#default_value' => !empty($element['#multiple']), '#attributes' => array('class' => 'multiple-toggle'), '#description' => t('Multiple values will let users select multiple items in this list.'), ); } // Add the main textfield for adding options. if (!isset($element['options'])) { $element['options_field'] = array( '#type' => 'textarea', '#resizable' => TRUE, '#cols' => 60, '#rows' => 5, '#value' => isset($element['#options']) ? form_options_to_text($element['#options'], $element['#key_type']) : '', '#required' => isset($element['#required']) ? $element['#required'] : FALSE, '#description' => t('List options one option per line.'), ); if ($element['#key_type'] == 'numeric' || $element['#key_type'] == 'custom') { $element['options_field']['#description'] .= ' ' . t('Key-value pairs may be specified by separating each option with pipes, such as key|value.'); } if (!empty($element['#key_type_toggle'])) { $element['options_field']['#description'] .= ' ' . t('If the %toggle field is checked, key-value pairs may be specified by separating each option with pipes, such as key|value.', array('%toggle' => $element['key_type']['#title'])); } if ($element['#key_type'] == 'numeric') { $element['options_field']['#description'] .= ' ' . t('This field requires all specified keys to be integers.'); } } // Add the field for storing default values. if (!isset($element['default_value_field'])) { $element['default_value_field'] = array( '#title' => t('Default value'), '#type' => 'textfield', '#size' => 60, '#value' => isset($element['#default_value']) ? ($element['#multiple'] ? implode(', ', (array) $element['#default_value']) : $element['#default_value']) : '', '#description' => t('Specify the keys that should be selected by default.'), ); if ($element['#multiple']) { $element['default_value_field']['#description'] .= ' ' . t('Multiple default values may be specified by separating keys with commas.'); } } // Remove properties that will confuse the FAPI. unset($element['#options']); $element['#required'] = FALSE; return $element; } /** * Validate the "options" form element type. * * This function adjusts the value of the element from a text value to an array. */ function form_type_options_value(&$element, $edit = FALSE) { if ($edit === FALSE) { return array( 'options' => isset($element['#options']) ? $element['#options'] : array(), 'default_value' => isset($element['#default_value']) ? $element['#default_value'] : '', ); } else { // Change the key type if a new type is specified. if (!empty($element['#key_type_toggle'])) { $element['#key_type'] = empty($edit['key_type']) ? 'associative' : 'custom'; } // Convert text to an array of options. $duplicates = array(); $options = form_options_from_text($edit['options_field'], $element['#key_type'], empty($element['#optgroups']), $duplicates); // Check if a key is used multiple times. if (count($duplicates) == 1) { form_error($element, t('The key %key has been used multiple times. Each key must be unique to display properly.', array('%key' => array_pop($duplicates)))); } elseif (!empty($duplicates)) { array_walk($duplicates, 'check_plain'); $duplicate_list = theme('item_list', $duplicates); form_error($element, t('The following keys have been used multiple times. Each key must be unique to display properly.') . $duplicate_list); } // Check if no options are specified. if (empty($options) && $element['#required']) { form_error($element, t('At least one option must be specified.')); } // Check for numeric keys if needed. if ($element['#key_type'] == 'numeric') { foreach ($options as $key => $value) { if (!is_int($key)) { form_error($element, t('The keys for the %title field must be integers.', array('%title' => $element['#title']))); break; } } } // Check that the limit of options has not been exceeded. if (!empty($element['#limit'])) { $count = 0; foreach ($options as $value) { if (is_array($value)) { $count += count($value); } else { $count++; } } if ($count > $element['#limit']) { form_error($element, t('The %title field supports a maximum of @count options. Please reduce the number of options.', array('%title' => $element['#title'], '@count' => $element['#limit']))); } } // Convert default value. if ($element['#multiple']) { $default_value = explode(',', $edit['default_value_field']); foreach ($default_value as $key => &$value) { $value = trim($value); if ($value === '') { unset($default_value[$key]); } } } else { $default_value = $edit['default_value_field']; } return array( 'options' => $options, 'default_value' => $default_value, ); } } function theme_options($element) { drupal_add_js('misc/tabledrag.js'); drupal_add_js(drupal_get_path('module', 'options_element') .'/options_element.js'); drupal_add_css(drupal_get_path('module', 'options_element') .'/options_element.css'); $classes = array(); if (isset($element['#attributes']['class'])) { $classes[] = $element['#attributes']['class']; } $classes[] = 'form-options'; $classes[] = 'options-key-type-'. $element['#key_type']; if (isset($element['#optgroups']) && $element['#optgroups']) { $classes[] = 'options-optgroups'; } if (isset($element['#multiple']) && $element['#multiple']) { $classes[] = 'options-multiple'; } $options = ''; $options .= drupal_render($element['options_field']); $options .= drupal_render($element['default_value_field']); $settings = ''; if (isset($element['key_type'])) { $settings .= drupal_render($element['key_type']); } if (isset($element['multiple'])) { $settings .= drupal_render($element['multiple']); } $output = ''; $output .= '
'; $output .= theme('fieldset', array( '#title' => t('Options'), '#collapsible' => FALSE, '#children' => $options, '#attributes' => array('class' => 'options'), )); if (!empty($settings)) { $output .= theme('fieldset', array( '#title' => t('Option settings'), '#collapsible' => FALSE, '#children' => $settings, '#attributes' => array('class' => 'option-settings'), )); } $output .= '
'; return $output; } /** * Create a textual representation of options from an array. * * @param $options * An array of options used in a select list. * @param $key_type * How key/value pairs should be interpreted. Available options: * - numeric * - associative * - custom * - none */ function form_options_to_text($options, $key_type) { $output = ''; $previous_key = false; $all_options = array(); foreach ($options as $key => $value) { // Convert groups. if (is_array($value)) { $output .= '<' . $key . '>' . "\n"; foreach ($value as $subkey => $subvalue) { $output .= (($key_type == 'numeric' || $key_type == 'custom') ? $subkey . '|' : '') . $subvalue . "\n"; } $previous_key = $key; } // Typical key|value pairs. else { // Exit out of any groups. if (isset($options[$previous_key]) && is_array($options[$previous_key])) { $output .= "<>\n"; } // Skip empty rows. if ($options[$key] !== '') { if ($key_type == 'numeric' || $key_type == 'custom') { $output .= $key . '|' . $value . "\n"; } else { $output .= $value . "\n"; } } $previous_key = $key; } } return $output; } /** * Submit function for option lists for radios, checkboxes, or select lists. * * If the Key of the option is within < >, treat as an optgroup * * * creates an optgroup with the label "Group 1" * * <> * Unsets the current group, allowing items to be inserted at the root element. */ function form_options_from_text($text, $key_type, $flat = FALSE, &$duplicates = array()) { $options = array(); $rows = array_filter(explode("\n", trim($text))); $group = FALSE; foreach ($rows as $option) { $option = trim($option); $matches = array(); // Check if this row is a group. if (!$flat && preg_match('/^\<([^>]*)\>$/', $option, $matches)) { if ($matches[1] === '') { $group = FALSE; } elseif (!$flat) { $group = $matches[1]; } } // Check if this row is a key|value pair. elseif (($key_type == 'custom' || $key_type == 'numeric') && preg_match('/^([^|]+)\|(.*)$/', $option, $matches)) { $key = $matches[1]; $value = $matches[2]; if ($group !== FALSE) { isset($options[$group][$key]) ? $duplicates[$key] = $key : $options[$group][$key] = $value; } else { isset($options[$key]) ? $duplicates[$key] = $key : $options[$key] = $value; } } // Check if this row is a straight value. else { if ($group !== FALSE) { if ($key_type != 'none') { isset($options[$group][$option]) ? $duplicates[$option] = $option : $options[$group][$option] = $option; } else { $options[$group][] = $option; } } else { if ($key_type != 'none') { isset($options[$option]) ? $duplicates[$option] = $option : $options[$option] = $option; } else { $options[] = $option; } } } } return $options; }