diff --git a/core/includes/form.inc b/core/includes/form.inc index 5d4a943..ea61906 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -8,6 +8,7 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\Number; use Drupal\Component\Utility\String; +use Drupal\Component\Utility\Xss; use Drupal\Component\Utility\Url; use Drupal\Core\Database\Database; use Drupal\Core\Language\Language; @@ -2687,6 +2688,9 @@ function form_process_file($element) { /** * Returns HTML for a form element. + * Prepares variables for form element templates. + * + * Default template: form-element.html.twig. * * Each form element is wrapped in a DIV container having the following CSS * classes: @@ -2723,7 +2727,7 @@ function form_process_file($element) { * but the parent element should have neither. Use this carefully because a * field without an associated label can cause accessibility challenges. * - * @param $variables + * @param array $variables * An associative array containing: * - element: An associative array containing the properties of the element. * Properties used: #title, #title_display, #description, #id, #required, @@ -2731,8 +2735,8 @@ function form_process_file($element) { * * @ingroup themeable */ -function theme_form_element($variables) { - $element = &$variables['element']; +function template_preprocess_form_element(&$variables) { + $element = $variables['element']; // This function is invoked as theme wrapper, but the rendered form element // may not necessarily have been processed by form_builder(). @@ -2740,67 +2744,85 @@ function theme_form_element($variables) { '#title_display' => 'before', ); + // If #title is not set, we don't display any label or required marker. + if (!isset($element['#title'])) { + $element['#title_display'] = 'none'; + } + + // If the element is required, a required marker is appended to the label. + $variables['required'] = array(); + if (!empty($element['#required'])) { + $variables['required'] = array( + '#theme' => 'form_required_marker', + '#element' => $element, + ); + } + + $variables['label']['attributes'] = array(); + // Style the label as class option to display inline with the element. + if ($element['#title_display'] == 'after') { + $variables['label']['attributes']['class'][] = 'option'; + } + // Show label only to screen readers to avoid disruption in visual flows. + elseif ($element['#title_display'] == 'invisible') { + $variables['label']['attributes']['class'][] = 'visually-hidden'; + } + + if (!empty($element['#id'])) { + $variables['label']['attributes']['for'] = $element['#id']; + } + + // Create an Attribute object from the label attributes. + if (!empty($variables['label']['attributes'])) { + $variables['label']['attributes'] = new Attribute($variables['label']['attributes']); + } + // Take over any #wrapper_attributes defined by the element. // @todo Temporary hack for #type 'item'. // @see http://drupal.org/node/1829202 + $variables['attributes'] = array(); if (isset($element['#wrapper_attributes'])) { - $attributes = $element['#wrapper_attributes']; + $variables['attributes'] = $element['#wrapper_attributes']; } + // Add element #id for #type 'item'. if (isset($element['#markup']) && !empty($element['#id'])) { - $attributes['id'] = $element['#id']; + $variables['attributes']['id'] = $element['#id']; } + // Add element's #type and #name as class to aid with JS/CSS selectors. - $attributes['class'][] = 'form-item'; + $variables['attributes']['class'][] = 'form-item'; if (!empty($element['#type'])) { - $attributes['class'][] = 'form-type-' . strtr($element['#type'], '_', '-'); + $variables['attributes']['class'][] = 'form-type-' . strtr($element['#type'], '_', '-'); } if (!empty($element['#name'])) { - $attributes['class'][] = 'form-item-' . strtr($element['#name'], array(' ' => '-', '_' => '-', '[' => '-', ']' => '')); + $variables['attributes']['class'][] = 'form-item-' . strtr($element['#name'], array(' ' => '-', '_' => '-', '[' => '-', ']' => '')); } // Add a class for disabled elements to facilitate cross-browser styling. if (!empty($element['#attributes']['disabled'])) { - $attributes['class'][] = 'form-disabled'; + $variables['attributes']['class'][] = 'form-disabled'; } - $output = '' . "\n"; // If #title is not set, we don't display any label or required marker. if (!isset($element['#title'])) { $element['#title_display'] = 'none'; } - $prefix = isset($element['#field_prefix']) ? '' . $element['#field_prefix'] . ' ' : ''; - $suffix = isset($element['#field_suffix']) ? ' ' . $element['#field_suffix'] . '' : ''; - - switch ($element['#title_display']) { - case 'before': - case 'invisible': - $output .= ' ' . theme('form_element_label', $variables); - $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n"; - break; - - case 'after': - $output .= ' ' . $prefix . $element['#children'] . $suffix; - $output .= ' ' . theme('form_element_label', $variables) . "\n"; - break; - - case 'none': - case 'attribute': - // Output no label and no required marker, only the children. - $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n"; - break; - } + $variables['prefix'] = isset($element['#field_prefix']) ? $element['#field_prefix'] : NULL; + $variables['suffix'] = isset($element['#field_suffix']) ? $element['#field_suffix'] : NULL; + $variables['description'] = NULL; if (!empty($element['#description'])) { - $attributes = array('class' => 'description'); + $description_attributes = array('class' => 'description'); if (!empty($element['#id'])) { - $attributes['id'] = $element['#id'] . '--description'; + $description_attributes['id'] = $element['#id'] . '--description'; } - $output .= '' . $element['#description'] . "\n"; + $variables['description']['attributes'] = new Attribute($description_attributes); + $variables['description']['content'] = $element['#description']; } - $output .= "\n"; - - return $output; + $variables['label']['display'] = $element['#title_display']; + $variables['label']['title'] = (isset($element['#title']) && $element['#title'] !== '') ? Xss::filterAdmin($element['#title']) : ''; + $variables['children'] = $element['#children']; } /** @@ -2821,59 +2843,6 @@ function theme_form_required_marker($variables) { } /** - * Returns HTML for a form element label and required marker. - * - * Form element labels include the #title and a #required marker. The label is - * associated with the element itself by the element #id. Labels may appear - * before or after elements, depending on theme_form_element() and - * #title_display. - * - * This function will not be called for elements with no labels, depending on - * #title_display. For elements that have an empty #title and are not required, - * this function will output no label (''). For required elements that have an - * empty #title, this will output the required marker alone within the label. - * The label will use the #id to associate the marker with the field that is - * required. That is especially important for screenreader users to know - * which field is required. - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties of the element. - * Properties used: #required, #title, #id, #value, #description. - * - * @ingroup themeable - */ -function theme_form_element_label($variables) { - $element = $variables['element']; - - // If title and required marker are both empty, output no label. - if ((!isset($element['#title']) || $element['#title'] === '') && empty($element['#required'])) { - return ''; - } - - // If the element is required, a required marker is appended to the label. - $required = !empty($element['#required']) ? theme('form_required_marker', array('element' => $element)) : ''; - - $title = filter_xss_admin($element['#title']); - - $attributes = array(); - // Style the label as class option to display inline with the element. - if ($element['#title_display'] == 'after') { - $attributes['class'] = 'option'; - } - // Show label only to screen readers to avoid disruption in visual flows. - elseif ($element['#title_display'] == 'invisible') { - $attributes['class'] = 'visually-hidden'; - } - - if (!empty($element['#id'])) { - $attributes['for'] = $element['#id']; - } - - return '' . t('!title!required', array('!title' => $title, '!required' => $required)) . ''; -} - -/** * Sets a form element's class attribute. * * Adds 'required' and 'error' classes as needed. diff --git a/core/includes/theme.inc b/core/includes/theme.inc index bfd1aaf..49d374f 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -2724,13 +2724,11 @@ function drupal_common_theme() { ), 'form_element' => array( 'render element' => 'element', + 'template' => 'form-element', ), 'form_required_marker' => array( 'render element' => 'element', ), - 'form_element_label' => array( - 'render element' => 'element', - ), 'vertical_tabs' => array( 'render element' => 'element', 'template' => 'vertical-tabs', diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index d4c1039..5381860 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -233,7 +233,7 @@ function hook_queue_info_alter(&$queues) { * - "#post_render": array of callables taking $children and $element. * - "#submit": array of callback functions taking $form and $form_state. * - "#title_display": optional string indicating if and how #title should be - * displayed, see theme_form_element() and theme_form_element_label(). + * displayed, see form_element.html.twig. * * @see hook_element_info_alter() * @see system_element_info() diff --git a/core/modules/system/templates/form-element.html.twig b/core/modules/system/templates/form-element.html.twig new file mode 100644 index 0000000..85fc5f5 --- /dev/null +++ b/core/modules/system/templates/form-element.html.twig @@ -0,0 +1,60 @@ +{# +/** + * @file + * Default theme implementation for a form element. + * + * Available variables: + * - attributes: HTML attributes for the containing element. + * - prefix: (optional) The form element prefix, may not be set. + * - suffix: (optional) The form element suffix, may not be set. + * - required: The required marker, or empty if the associated form element is + * not required. + * - label: A list of label properties containing: + * - title: The label's text. + * - attributes: A list of HTML attributes for the label. + * - display: Label display setting. It can have these values: + * - before: The label is output before the element. This is the default. + * The label includes the #title and the required marker, if #required. + * - after: The label is output after the element. For example, this is used + * for radio and checkbox #type elements as set in system_element_info(). + * If the #title is empty but the field is #required, the label will + * contain only the required marker. + * - invisible: Labels are critical for screen readers to enable them to + * properly navigate through forms but can be visually distracting. This + * property hides the label for everyone except screen readers. + * - attribute: Set the title attribute on the element to create a tooltip but + * output no label element. This is supported only for checkboxes and radios + * in form_pre_render_conditional_form_element(). It is used where a visual + * label is not needed, such as a table of checkboxes where the row and + * column provide the context. The tooltip will include the title and + * required marker. + * - description: (optional) A list of description properties containing: + * - content: A description of the form element, may not be set. + * - attributes: (optional) A list of HTML attributes to apply to the + * description content wrapper. Will only be set when description is set. + * + * @see template_preprocess_form_element() + * + * @ingroup themeable + */ +#} + + {% if label.display in ['before', 'invisible'] and (label.title is not empty or required) %} + {{ label.title }}{{ required }} + {% endif %} + {% if prefix is not empty %} + {{ prefix }} + {% endif %} + {{ children }} + {% if suffix is not empty %} + {{ suffix }} + {% endif %} + {% if label.display == 'after' and (label.title is not empty or required) %} + {{ label.title }}{{ required }} + {% endif %} + {% if description.content %} + + {{ description.content }} + + {% endif %} +