Skip to content
Snippets Groups Projects
Unverified Commit 20c250eb authored by Alex Pott's avatar Alex Pott
Browse files

Issue #2497909 by Andrew.Mikhailov, pfrenssen, claudiu.cristea, cilefen,...

Issue #2497909 by Andrew.Mikhailov, pfrenssen, claudiu.cristea, cilefen, yogeshmpawar, jibran, kim.pepper, alexpott, Jeremy JOCHEMSKI, Cottser: Duplicate HTML IDs are created for file_managed_file fields
parent 3346d45f
No related branches found
No related tags found
No related merge requests found
...@@ -423,11 +423,14 @@ function template_preprocess_textarea(&$variables) { ...@@ -423,11 +423,14 @@ function template_preprocess_textarea(&$variables) {
* but the parent element should have neither. Use this carefully because a * but the parent element should have neither. Use this carefully because a
* field without an associated label can cause accessibility challenges. * field without an associated label can cause accessibility challenges.
* *
* To associate the label with a different field, set the #label_for property
* to the ID of the desired field.
*
* @param array $variables * @param array $variables
* An associative array containing: * An associative array containing:
* - element: An associative array containing the properties of the element. * - element: An associative array containing the properties of the element.
* Properties used: #title, #title_display, #description, #id, #required, * Properties used: #title, #title_display, #description, #id, #required,
* #children, #type, #name. * #children, #type, #name, #label_for.
*/ */
function template_preprocess_form_element(&$variables) { function template_preprocess_form_element(&$variables) {
$element = &$variables['element']; $element = &$variables['element'];
...@@ -439,6 +442,7 @@ function template_preprocess_form_element(&$variables) { ...@@ -439,6 +442,7 @@ function template_preprocess_form_element(&$variables) {
'#title_display' => 'before', '#title_display' => 'before',
'#wrapper_attributes' => [], '#wrapper_attributes' => [],
'#label_attributes' => [], '#label_attributes' => [],
'#label_for' => NULL,
]; ];
$variables['attributes'] = $element['#wrapper_attributes']; $variables['attributes'] = $element['#wrapper_attributes'];
...@@ -487,6 +491,12 @@ function template_preprocess_form_element(&$variables) { ...@@ -487,6 +491,12 @@ function template_preprocess_form_element(&$variables) {
$variables['label'] = ['#theme' => 'form_element_label']; $variables['label'] = ['#theme' => 'form_element_label'];
$variables['label'] += array_intersect_key($element, array_flip(['#id', '#required', '#title', '#title_display'])); $variables['label'] += array_intersect_key($element, array_flip(['#id', '#required', '#title', '#title_display']));
$variables['label']['#attributes'] = $element['#label_attributes']; $variables['label']['#attributes'] = $element['#label_attributes'];
if (!empty($element['#label_for'])) {
$variables['label']['#for'] = $element['#label_for'];
if (!empty($element['#id'])) {
$variables['label']['#id'] = $element['#id'] . '--label';
}
}
$variables['children'] = $element['#children']; $variables['children'] = $element['#children'];
} }
...@@ -507,10 +517,13 @@ function template_preprocess_form_element(&$variables) { ...@@ -507,10 +517,13 @@ function template_preprocess_form_element(&$variables) {
* required. That is especially important for screenreader users to know * required. That is especially important for screenreader users to know
* which field is required. * which field is required.
* *
* To associate the label with a different field, set the #for property to the
* ID of the desired field.
*
* @param array $variables * @param array $variables
* An associative array containing: * An associative array containing:
* - element: An associative array containing the properties of the element. * - element: An associative array containing the properties of the element.
* Properties used: #required, #title, #id, #value, #description. * Properties used: #required, #title, #id, #value, #description, #for.
*/ */
function template_preprocess_form_element_label(&$variables) { function template_preprocess_form_element_label(&$variables) {
$element = $variables['element']; $element = $variables['element'];
......
...@@ -305,12 +305,24 @@ public static function processManagedFile(&$element, FormStateInterface $form_st ...@@ -305,12 +305,24 @@ public static function processManagedFile(&$element, FormStateInterface $form_st
$element['upload_button']['#ajax']['event'] = 'fileUpload'; $element['upload_button']['#ajax']['event'] = 'fileUpload';
} }
// Use a manually generated ID for the file upload field so the desired
// field label can be associated with it below. Use the same method for
// setting the ID that the form API autogenerator does.
// @see \Drupal\Core\Form\FormBuilder::doBuildForm()
$id = Html::getUniqueId('edit-' . implode('-', array_merge($element['#parents'], ['upload'])));
// The file upload field itself. // The file upload field itself.
$element['upload'] = [ $element['upload'] = [
'#name' => 'files[' . $parents_prefix . ']', '#name' => 'files[' . $parents_prefix . ']',
'#type' => 'file', '#type' => 'file',
// This #title will not actually be used as the upload field's HTML label,
// since the theme function for upload fields never passes the element
// through theme('form_element'). Instead the parent element's #title is
// used as the label (see below). That is usually a more meaningful label
// anyway.
'#title' => t('Choose a file'), '#title' => t('Choose a file'),
'#title_display' => 'invisible', '#title_display' => 'invisible',
'#id' => $id,
'#size' => $element['#size'], '#size' => $element['#size'],
'#multiple' => $element['#multiple'], '#multiple' => $element['#multiple'],
'#theme_wrappers' => [], '#theme_wrappers' => [],
...@@ -321,6 +333,10 @@ public static function processManagedFile(&$element, FormStateInterface $form_st ...@@ -321,6 +333,10 @@ public static function processManagedFile(&$element, FormStateInterface $form_st
$element['upload']['#attributes'] = ['accept' => $element['#accept']]; $element['upload']['#attributes'] = ['accept' => $element['#accept']];
} }
// Indicate that $element['#title'] should be used as the HTML label for the
// file upload field.
$element['#label_for'] = $element['upload']['#id'];
if (!empty($fids) && $element['#files']) { if (!empty($fids) && $element['#files']) {
foreach ($element['#files'] as $delta => $file) { foreach ($element['#files'] as $delta => $file) {
$file_link = [ $file_link = [
...@@ -353,13 +369,9 @@ public static function processManagedFile(&$element, FormStateInterface $form_st ...@@ -353,13 +369,9 @@ public static function processManagedFile(&$element, FormStateInterface $form_st
// Add the extension list to the page as JavaScript settings. // Add the extension list to the page as JavaScript settings.
if (isset($element['#upload_validators']['file_validate_extensions'][0])) { if (isset($element['#upload_validators']['file_validate_extensions'][0])) {
$extension_list = implode(',', array_filter(explode(' ', $element['#upload_validators']['file_validate_extensions'][0]))); $extension_list = implode(',', array_filter(explode(' ', $element['#upload_validators']['file_validate_extensions'][0])));
$element['upload']['#attached']['drupalSettings']['file']['elements']['#' . $element['#id']] = $extension_list; $element['upload']['#attached']['drupalSettings']['file']['elements']['#' . $id] = $extension_list;
} }
// Let #id point to the file element, so the field label's 'for' corresponds
// with it.
$element['#id'] = &$element['upload']['#id'];
// Prefix and suffix used for Ajax replacement. // Prefix and suffix used for Ajax replacement.
$element['#prefix'] = '<div id="' . $ajax_wrapper_id . '">'; $element['#prefix'] = '<div id="' . $ajax_wrapper_id . '">';
$element['#suffix'] = '</div>'; $element['#suffix'] = '</div>';
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
namespace Drupal\Tests\file\Functional; namespace Drupal\Tests\file\Functional;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\file\Entity\File; use Drupal\file\Entity\File;
use Drupal\node\Entity\Node; use Drupal\node\Entity\Node;
...@@ -107,6 +108,9 @@ public function testNodeDisplay() { ...@@ -107,6 +108,9 @@ public function testNodeDisplay() {
$this->assertRaw($field_name . '[0][display]', 'First file appears as expected.'); $this->assertRaw($field_name . '[0][display]', 'First file appears as expected.');
$this->assertRaw($field_name . '[1][display]', 'Second file appears as expected.'); $this->assertRaw($field_name . '[1][display]', 'Second file appears as expected.');
$this->assertSession()->responseContains($field_name . '[1][description]', 'Description of second file appears as expected.'); $this->assertSession()->responseContains($field_name . '[1][description]', 'Description of second file appears as expected.');
// Check that the file fields don't contain duplicate HTML IDs.
$this->assertNoDuplicateIds();
} }
/** /**
...@@ -220,4 +224,32 @@ public function testDescriptionDefaultFileFieldDisplay() { ...@@ -220,4 +224,32 @@ public function testDescriptionDefaultFileFieldDisplay() {
$this->assertFieldByXPath('//a[@href="' . $node->{$field_name}->entity->url() . '"]', $description); $this->assertFieldByXPath('//a[@href="' . $node->{$field_name}->entity->url() . '"]', $description);
} }
/**
* Asserts that each HTML ID is used for just a single element on the page.
*
* @param string $message
* (optional) A message to display with the assertion.
*/
protected function assertNoDuplicateIds($message = '') {
$args = ['@url' => $this->getUrl()];
if (!$elements = $this->xpath('//*[@id]')) {
$this->fail(new FormattableMarkup('The page @url contains no HTML IDs.', $args));
return;
}
$message = $message ?: new FormattableMarkup('The page @url does not contain duplicate HTML IDs', $args);
$seen_ids = [];
foreach ($elements as $element) {
$id = $element->getAttribute('id');
if (isset($seen_ids[$id])) {
$this->fail($message);
return;
}
$seen_ids[$id] = TRUE;
}
$this->assertTrue(TRUE, $message);
}
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment