diff --git a/core/includes/form.inc b/core/includes/form.inc index 967b88c..eb68923 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -4720,35 +4720,42 @@ function theme_form_element($variables) { if (!empty($element['#attributes']['disabled'])) { $attributes['class'][] = 'form-disabled'; } - $output = '' . "\n"; + $output = "\n" . '' . "\n"; - // If #title is not set, we don't even call theme_form_element_label. + // If #title or #description is not set at all, unset the respective display + // property, so the theme layer isn't unnecessarily called. if (!isset($element['#title'])) { $element['#title_display'] = 'none'; } - // If #description is not set, we don't even call - // theme_form_element_description. if (!isset($element['#description'])) { $element['#description_display'] == 'none'; } + // Print #title and #decription if it should be displayed before the element. if ($element['#title_display'] == 'before' || $element['#title_display'] == 'invisible') { - $output .= ' ' . theme('form_element_label', $variables); + $output .= theme('form_element_label', $variables); } if ($element['#description_display'] == 'before') { - $output .= "\n" . theme('form_element_description', $variables) . "\n"; + $output .= theme('form_element_description', $variables); } + // Prepend a field prefix if given. + if (isset($element['#field_prefix'])) { + $output .= ' ' . $element['#field_prefix'] . '' . "\n"; + } // Render the element's children. - $output .= isset($element['#field_prefix']) ? '' . $element['#field_prefix'] . ' ' : ' '; - $output .= $element['#children']; - $output .= isset($element['#field_suffix']) ? ' ' . $element['#field_suffix'] . '' : ' '; + $output .= ' ' . $element['#children'] . "\n"; + // Append a field suffix if given. + if (isset($element['#field_suffix'])) { + $output .= ' ' . $element['#field_suffix'] . '' . "\n"; + } + // Print #title and #decription if it should be displayed after the element. if ($element['#title_display'] == 'after') { - $output .= ' ' . theme('form_element_label', $variables); + $output .= theme('form_element_label', $variables); } if ($element['#description_display'] == 'after' || $element['#description_display'] == 'invisible') { - $output .= "\n" . theme('form_element_description', $variables) . "\n"; + $output .= theme('form_element_description', $variables); } $output .= '' . "\n"; @@ -4823,7 +4830,7 @@ function theme_form_element_label($variables) { $attributes['for'] = $element['#id']; } - return '' . t('!title!required', array('!title' => $title, '!required' => $required)) . ''; + return ' ' . t('!title!required', array('!title' => $title, '!required' => $required)) . '' . "\n"; } /** diff --git a/core/modules/system/lib/Drupal/system/Tests/Form/ElementsLabelsTest.php b/core/modules/system/lib/Drupal/system/Tests/Form/ElementsLabelsTest.php index feb354b..8eb3578 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Form/ElementsLabelsTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Form/ElementsLabelsTest.php @@ -23,8 +23,8 @@ class ElementsLabelsTest extends WebTestBase { public static function getInfo() { return array( - 'name' => 'Form element and label output test', - 'description' => 'Test form element labels, required markers and associated output.', + 'name' => 'Form element label and description test', + 'description' => 'Test form element labels, required markers, descriptions and associated output.', 'group' => 'Form API', ); } @@ -97,4 +97,30 @@ function testFormLabels() { $elements = $this->xpath('//div[@id="edit-form-radios-title-attribute"]'); $this->assertEqual($elements[0]['title'], 'Radios test' . ' (' . t('Required') . ')', 'Title attribute found.'); } + + /** + * Test description of form elements with different placement options. + */ + function testFormDescriptions() { + $test = $this->drupalGet('form_test/form-descriptions'); + + // Check #description placement with #description_display='after'. + $field_id = 'edit-form-textfield-test-description-after'; + $description_id = $field_id . '--description'; + $elements = $this->xpath('//input[@id="' . $field_id . '" and @aria-describedby="' . $description_id . '"]/following-sibling::div[@id="' . $description_id . '"]'); + $this->assertTrue(isset($elements[0]), t('Properly places the #description element after the form item.')); + + // Check #description placement with #description_display='before'. + $field_id = 'edit-form-textfield-test-description-before'; + $description_id = $field_id . '--description'; + $elements = $this->xpath('//input[@id="' . $field_id . '" and @aria-describedby="' . $description_id . '"]/preceding-sibling::div[@id="' . $description_id . '"]'); + $this->assertTrue(isset($elements[0]), t('Properly places the #description element before the form item.')); + + // Check #description placement with #description_display='invisible'. + $field_id = 'edit-form-textfield-test-description-invisible'; + $description_id = $field_id . '--description'; + $elements = $this->xpath('//input[@id="' . $field_id . '" and @aria-describedby="' . $description_id . '"]/following-sibling::div[@class="visually-hidden"]'); + $this->assertTrue(isset($elements[0]), t('Properly renders the #description element visually-hidden.')); + } + } diff --git a/core/modules/system/tests/modules/form_test/form_test.module b/core/modules/system/tests/modules/form_test/form_test.module index cb56e2b..5ff635a 100644 --- a/core/modules/system/tests/modules/form_test/form_test.module +++ b/core/modules/system/tests/modules/form_test/form_test.module @@ -261,6 +261,14 @@ function form_test_menu() { 'type' => MENU_CALLBACK, ); + $items['form_test/form-descriptions'] = array( + 'title' => 'Form description test', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('form_description_test_form'), + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + $items['form-test/state-persist'] = array( 'title' => 'Form state persistence without storage', 'page callback' => 'drupal_get_form', @@ -1063,6 +1071,34 @@ function form_label_test_form() { } /** + * A form for testing placement of form descriptions. + */ +function form_description_test_form() { + $form['form_textfield_test_description_before'] = array( + '#type' =>'textfield', + '#title' => t('Textfield test for description before element'), + '#description' => t('Textfield test for description before element'), + '#description_display' => 'before' + ); + + $form['form_textfield_test_description_after'] = array( + '#type' =>'textfield', + '#title' => t('Textfield test for description after element'), + '#description' => t('Textfield test for description after element'), + '#description_display' => 'after' + ); + + $form['form_textfield_test_description_invisible'] = array( + '#type' =>'textfield', + '#title' => t('Textfield test for visually-hidden description'), + '#description' => t('Textfield test for visually-hidden description'), + '#description_display' => 'invisible' + ); + + return $form; +} + +/** * Menu callback; Invokes a form builder function with a wrapper callback. */ function form_test_wrapper_callback($form_id) {