Select.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. <?php
  2. namespace Drupal\Core\Render\Element;
  3. use Drupal\Core\Form\FormStateInterface;
  4. use Drupal\Core\Render\Element;
  5. /**
  6. * Provides a form element for a drop-down menu or scrolling selection box.
  7. *
  8. * Properties:
  9. * - #options: An associative array, where the keys are the values for each
  10. * option, and the values are the option labels to be shown in the drop-down
  11. * list. If a value is an array, it will be rendered similarly, but as an
  12. * optgroup. The key of the sub-array will be used as the label for the
  13. * optgroup. Nesting optgroups is not allowed.
  14. * - #empty_option: (optional) The label to show for the first default option.
  15. * By default, the label is automatically set to "- Select -" for a required
  16. * field and "- None -" for an optional field.
  17. * - #empty_value: (optional) The value for the first default option, which is
  18. * used to determine whether the user submitted a value or not.
  19. * - If #required is TRUE, this defaults to '' (an empty string).
  20. * - If #required is not TRUE and this value isn't set, then no extra option
  21. * is added to the select control, leaving the control in a slightly
  22. * illogical state, because there's no way for the user to select nothing,
  23. * since all user agents automatically preselect the first available
  24. * option. But people are used to this being the behavior of select
  25. * controls.
  26. * @todo Address the above issue in Drupal 8.
  27. * - If #required is not TRUE and this value is set (most commonly to an
  28. * empty string), then an extra option (see #empty_option above)
  29. * representing a "non-selection" is added with this as its value.
  30. * - #multiple: (optional) Indicates whether one or more options can be
  31. * selected. Defaults to FALSE.
  32. * - #default_value: Must be NULL or not set in case there is no value for the
  33. * element yet, in which case a first default option is inserted by default.
  34. * Whether this first option is a valid option depends on whether the field
  35. * is #required or not.
  36. * - #required: (optional) Whether the user needs to select an option (TRUE)
  37. * or not (FALSE). Defaults to FALSE.
  38. * - #size: The size of the input element in characters.
  39. *
  40. * Usage example:
  41. * @code
  42. * $form['example_select'] = [
  43. * '#type' => 'select',
  44. * '#title' => $this->t('Select element'),
  45. * '#options' => [
  46. * '1' => $this->t('One'),
  47. * '2' => [
  48. * '2.1' => $this->t('Two point one'),
  49. * '2.2' => $this->t('Two point two'),
  50. * ],
  51. * '3' => $this->t('Three'),
  52. * ],
  53. * ];
  54. * @endcode
  55. *
  56. * @FormElement("select")
  57. */
  58. class Select extends FormElement {
  59. /**
  60. * {@inheritdoc}
  61. */
  62. public function getInfo() {
  63. $class = get_class($this);
  64. return [
  65. '#input' => TRUE,
  66. '#multiple' => FALSE,
  67. '#process' => [
  68. [$class, 'processSelect'],
  69. [$class, 'processAjaxForm'],
  70. ],
  71. '#pre_render' => [
  72. [$class, 'preRenderSelect'],
  73. ],
  74. '#theme' => 'select',
  75. '#theme_wrappers' => ['form_element'],
  76. '#options' => [],
  77. ];
  78. }
  79. /**
  80. * Processes a select list form element.
  81. *
  82. * This process callback is mandatory for select fields, since all user agents
  83. * automatically preselect the first available option of single (non-multiple)
  84. * select lists.
  85. *
  86. * @param array $element
  87. * The form element to process.
  88. * @param \Drupal\Core\Form\FormStateInterface $form_state
  89. * The current state of the form.
  90. * @param array $complete_form
  91. * The complete form structure.
  92. *
  93. * @return array
  94. * The processed element.
  95. *
  96. * @see _form_validate()
  97. */
  98. public static function processSelect(&$element, FormStateInterface $form_state, &$complete_form) {
  99. // #multiple select fields need a special #name.
  100. if ($element['#multiple']) {
  101. $element['#attributes']['multiple'] = 'multiple';
  102. $element['#attributes']['name'] = $element['#name'] . '[]';
  103. }
  104. // A non-#multiple select needs special handling to prevent user agents from
  105. // preselecting the first option without intention. #multiple select lists do
  106. // not get an empty option, as it would not make sense, user interface-wise.
  107. else {
  108. // If the element is set to #required through #states, override the
  109. // element's #required setting.
  110. $required = isset($element['#states']['required']) ? TRUE : $element['#required'];
  111. // If the element is required and there is no #default_value, then add an
  112. // empty option that will fail validation, so that the user is required to
  113. // make a choice. Also, if there's a value for #empty_value or
  114. // #empty_option, then add an option that represents emptiness.
  115. if (($required && !isset($element['#default_value'])) || isset($element['#empty_value']) || isset($element['#empty_option'])) {
  116. $element += [
  117. '#empty_value' => '',
  118. '#empty_option' => $required ? t('- Select -') : t('- None -'),
  119. ];
  120. // The empty option is prepended to #options and purposively not merged
  121. // to prevent another option in #options mistakenly using the same value
  122. // as #empty_value.
  123. $empty_option = [$element['#empty_value'] => $element['#empty_option']];
  124. $element['#options'] = $empty_option + $element['#options'];
  125. }
  126. }
  127. return $element;
  128. }
  129. /**
  130. * {@inheritdoc}
  131. */
  132. public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
  133. if ($input !== FALSE) {
  134. if (isset($element['#multiple']) && $element['#multiple']) {
  135. // If an enabled multi-select submits NULL, it means all items are
  136. // unselected. A disabled multi-select always submits NULL, and the
  137. // default value should be used.
  138. if (empty($element['#disabled'])) {
  139. return (is_array($input)) ? array_combine($input, $input) : [];
  140. }
  141. else {
  142. return (isset($element['#default_value']) && is_array($element['#default_value'])) ? $element['#default_value'] : [];
  143. }
  144. }
  145. // Non-multiple select elements may have an empty option prepended to them
  146. // (see \Drupal\Core\Render\Element\Select::processSelect()). When this
  147. // occurs, usually #empty_value is an empty string, but some forms set
  148. // #empty_value to integer 0 or some other non-string constant. PHP
  149. // receives all submitted form input as strings, but if the empty option
  150. // is selected, set the value to match the empty value exactly.
  151. elseif (isset($element['#empty_value']) && $input === (string) $element['#empty_value']) {
  152. return $element['#empty_value'];
  153. }
  154. else {
  155. return $input;
  156. }
  157. }
  158. }
  159. /**
  160. * Prepares a select render element.
  161. */
  162. public static function preRenderSelect($element) {
  163. Element::setAttributes($element, ['id', 'name', 'size']);
  164. static::setAttributes($element, ['form-select']);
  165. return $element;
  166. }
  167. }