From 716a102849c7943b1ae4e260230d2f3309e9cd90 Mon Sep 17 00:00:00 2001 From: Lee Rowlands <lee.rowlands@previousnext.com.au> Date: Fri, 2 Feb 2024 07:18:48 +1000 Subject: [PATCH] Issue #3076054 by godotislate, joaopauloc.dev, smustgrave, nod_, nayana_mvr, larowlan, alexpott, lauriii, quietone, benjifisher, anup.singh, bnjmnm: Existing field items should not be validated when adding another item in widget for unlimited cardinality field --- core/lib/Drupal/Core/Field/WidgetBase.php | 17 ++++++-- .../MultipleValueWidgetTest.php | 41 +++++++++++++++++++ ...EntityFormFieldValidationFilteringTest.php | 4 +- 3 files changed, 57 insertions(+), 5 deletions(-) diff --git a/core/lib/Drupal/Core/Field/WidgetBase.php b/core/lib/Drupal/Core/Field/WidgetBase.php index 9e4de97ff602..6fdd8088099f 100644 --- a/core/lib/Drupal/Core/Field/WidgetBase.php +++ b/core/lib/Drupal/Core/Field/WidgetBase.php @@ -5,6 +5,9 @@ use Drupal\Component\Utility\Html; use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\SortArray; +use Drupal\Core\Ajax\AjaxResponse; +use Drupal\Core\Ajax\FocusFirstCommand; +use Drupal\Core\Ajax\InsertCommand; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Render\Element; @@ -280,7 +283,7 @@ protected function formMultipleElements(FieldItemListInterface $items, array &$f '#name' => strtr($id_prefix, '-', '_') . '_add_more', '#value' => t('Add another item'), '#attributes' => ['class' => ['field-add-more-submit']], - '#limit_validation_errors' => [array_merge($parents, [$field_name])], + '#limit_validation_errors' => [], '#submit' => [[static::class, 'addMoreSubmit']], '#ajax' => [ 'callback' => [static::class, 'addMoreAjax'], @@ -349,10 +352,18 @@ public static function addMoreAjax(array $form, FormStateInterface $form_state) // Add a DIV around the delta receiving the Ajax effect. $delta = $element['#max_delta']; - $element[$delta]['#prefix'] = '<div class="ajax-new-content">' . ($element[$delta]['#prefix'] ?? ''); + // Construct an attribute to add to div for use as selector to set the focus on. + $button_parent = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1)); + $focus_attribute = 'data-drupal-selector="field-' . $button_parent['#field_name'] . '-more-focus-target"'; + $element[$delta]['#prefix'] = '<div class="ajax-new-content" ' . $focus_attribute . '>' . ($element[$delta]['#prefix'] ?? ''); $element[$delta]['#suffix'] = ($element[$delta]['#suffix'] ?? '') . '</div>'; - return $element; + // Turn render array into response with AJAX commands. + $response = new AjaxResponse(); + $response->addCommand(new InsertCommand(NULL, $element)); + // Add command to set the focus on first focusable element within the div. + $response->addCommand(new FocusFirstCommand("[$focus_attribute]")); + return $response; } /** diff --git a/core/modules/field/tests/src/FunctionalJavascript/MultipleValueWidgetTest.php b/core/modules/field/tests/src/FunctionalJavascript/MultipleValueWidgetTest.php index 1b037e2ee17e..582bf3a4db89 100644 --- a/core/modules/field/tests/src/FunctionalJavascript/MultipleValueWidgetTest.php +++ b/core/modules/field/tests/src/FunctionalJavascript/MultipleValueWidgetTest.php @@ -169,4 +169,45 @@ public function testFieldMultipleValueWidget() { $this->assertSame('', $field_1->getValue()); } + /** + * Tests that no validation occurs on field on "Add more" click. + */ + public function testFieldMultipleValueWidgetAddMoreNoValidation() { + // Set unlimited field to be required. + $field_name = 'field_unlimited'; + $field = FieldConfig::loadByName('entity_test', 'entity_test', $field_name); + $field->setRequired(TRUE); + $field->save(); + + $this->drupalGet('entity_test/add'); + $assert_session = $this->assertSession(); + $page = $this->getSession()->getPage(); + + // Add another item with the first item being empty, even though the field + // is required. + $add_more_button = $page->findButton('field_unlimited_add_more'); + $add_more_button->click(); + $field_1 = $assert_session->waitForField('field_unlimited[1][value]'); + $this->assertNotEmpty($field_1, 'Successfully added another item.'); + // Confirm the new item has focus. + $this->assertHasFocusByAttribute('name', 'field_unlimited[1][value]'); + // The first item should not be in error state. + $assert_session->elementNotExists('css', 'input[name="field_unlimited[0][value]"].error'); + } + + /** + * Asserts an element specified by an attribute value has focus. + * + * @param string $name + * The attribute name. + * @param string $value + * The attribute value. + * + * @todo Replace with assertHasFocus() in https://drupal.org/i/3041768. + */ + private function assertHasFocusByAttribute(string $name, string $value): void { + $active_element = $this->getSession()->evaluateScript('document.activeElement'); + $this->assertSame($value, $active_element->attribute($name)); + } + } diff --git a/core/tests/Drupal/FunctionalTests/Entity/ContentEntityFormFieldValidationFilteringTest.php b/core/tests/Drupal/FunctionalTests/Entity/ContentEntityFormFieldValidationFilteringTest.php index 1cb3b7808e78..63765337939d 100644 --- a/core/tests/Drupal/FunctionalTests/Entity/ContentEntityFormFieldValidationFilteringTest.php +++ b/core/tests/Drupal/FunctionalTests/Entity/ContentEntityFormFieldValidationFilteringTest.php @@ -151,14 +151,14 @@ public function testFieldWidgetsWithLimitedValidationErrors() { $assert_session->elementExists('css', 'input#edit-test-file-0-remove-button'); // Make the 'Test multiple' field required and check that adding another - // item throws a validation error. + // item does not throw a validation error. $field_config = FieldConfig::loadByName($this->entityTypeId, $this->entityTypeId, $this->fieldNameMultiple); $field_config->setRequired(TRUE); $field_config->save(); $this->drupalGet($this->entityTypeId . '/add'); $this->submitForm([], 'Add another item'); - $assert_session->pageTextContains('Test multiple (value 1) field is required.'); + $assert_session->pageTextNotContains('Test multiple (value 1) field is required.'); // Check that saving the form without entering any value for the required // field still throws the proper validation errors. -- GitLab