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
3346d45f49
commit
20c250eb42
|
@ -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) {
|
||||||
'#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) {
|
||||||
$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) {
|
||||||
* 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 @@ class ManagedFile extends FormElement {
|
||||||
$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 @@ class ManagedFile extends FormElement {
|
||||||
$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 @@ class ManagedFile extends FormElement {
|
||||||
// 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 @@
|
||||||
|
|
||||||
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 @@ class FileFieldDisplayTest extends FileFieldTestBase {
|
||||||
$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 @@ class FileFieldDisplayTest extends FileFieldTestBase {
|
||||||
$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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue