From 6470ab797cbeca91318c5c5e7974ff419d77cea9 Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Tue, 1 Feb 2022 12:31:23 +0000 Subject: [PATCH] Issue #3115054 by chr.fritsch, vsujeetkumar, Vidushi Mehta, sergiuteaca, janmejaig, ranjith_kumar_k_u, phenaproxima: Media library widget forgets ordering when adding or removing items --- .../Field/FieldWidget/MediaLibraryWidget.php | 34 ++++++++ .../EntityReferenceWidgetTest.php | 86 +++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php index 3f341f54b06c..7e76940a0b35 100644 --- a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php +++ b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php @@ -286,6 +286,22 @@ public function form(FieldItemListInterface $items, array &$form, FormStateInter return parent::form($items, $form, $form_state, $get_delta); } + /** + * {@inheritdoc} + */ + public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) { + parent::extractFormValues($items, $form, $form_state); + + // Update reference to 'items' stored during add or remove to take into + // account changes to values like 'weight' etc. + // @see Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget::addItems + // @see Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget::removeItem + $field_name = $this->fieldDefinition->getName(); + $field_state = static::getWidgetState($form['#parents'], $field_name, $form_state); + $field_state['items'] = $items->getValue(); + static::setWidgetState($form['#parents'], $field_name, $form_state, $field_state); + } + /** * {@inheritdoc} */ @@ -744,7 +760,15 @@ public static function updateWidget(array $form, FormStateInterface $form_state) * The form state. */ public static function removeItem(array $form, FormStateInterface $form_state) { + // During the form rebuild, formElement() will create field item widget + // elements using re-indexed deltas, so clear out FormState::$input to + // avoid a mismatch between old and new deltas. The rebuilt elements will + // have #default_value set appropriately for the current state of the field, + // so nothing is lost in doing this. + // @see Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget::extractFormValues $triggering_element = $form_state->getTriggeringElement(); + $parents = array_slice($triggering_element['#parents'], 0, -2); + NestedArray::setValue($form_state->getUserInput(), $parents, NULL); // Get the parents required to find the top-level widget element. if (count($triggering_element['#array_parents']) < 4) { @@ -844,7 +868,17 @@ public static function validateItems(array $form, FormStateInterface $form_state * The form state. */ public static function addItems(array $form, FormStateInterface $form_state) { + // During the form rebuild, formElement() will create field item widget + // elements using re-indexed deltas, so clear out FormState::$input to + // avoid a mismatch between old and new deltas. The rebuilt elements will + // have #default_value set appropriately for the current state of the field, + // so nothing is lost in doing this. + // @see Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget::extractFormValues $button = $form_state->getTriggeringElement(); + $parents = array_slice($button['#parents'], 0, -1); + $parents[] = 'selection'; + NestedArray::setValue($form_state->getUserInput(), $parents, NULL); + $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1)); $field_state = static::getFieldState($element, $form_state); diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/EntityReferenceWidgetTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/EntityReferenceWidgetTest.php index 5ec06908ffec..31c00338290c 100644 --- a/core/modules/media_library/tests/src/FunctionalJavascript/EntityReferenceWidgetTest.php +++ b/core/modules/media_library/tests/src/FunctionalJavascript/EntityReferenceWidgetTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\media_library\FunctionalJavascript; use Drupal\field\Entity\FieldConfig; +use Drupal\FunctionalJavascriptTests\SortableTestTrait; /** * Tests the Media library entity reference widget. @@ -11,6 +12,8 @@ */ class EntityReferenceWidgetTest extends MediaLibraryTestBase { + use SortableTestTrait; + /** * {@inheritdoc} */ @@ -256,6 +259,7 @@ public function testWidget() { $this->openMediaLibraryForField('field_twin_media'); $page->checkField('Select Dog'); $this->pressInsertSelected('Added one media item.'); + $this->waitForElementsCount('css', '.field--name-field-twin-media [data-media-library-item-delta]', 2); // Assert that we can toggle the visibility of the weight inputs when the // field contains more than one item. $wrapper = $assert_session->elementExists('css', '.field--name-field-twin-media'); @@ -485,4 +489,86 @@ public function testRequiredMediaField() { $this->assertSession()->pageTextContains('Basic page My page has been created.'); } + /** + * Tests that changed order is maintained after removing a selection. + */ + public function testRemoveAfterReordering(): void { + $assert_session = $this->assertSession(); + $page = $this->getSession()->getPage(); + + $this->drupalGet('node/add/basic_page'); + $page->fillField('Title', 'My page'); + + $this->openMediaLibraryForField('field_unlimited_media'); + $page->checkField('Select Dog'); + $page->checkField('Select Cat'); + $page->checkField('Select Bear'); + // Order: Dog - Cat - Bear. + $this->pressInsertSelected('Added 3 media items.'); + + // Move first item (Dog) to the end. + // Order: Cat - Bear - Dog. + $this->sortableAfter('[data-media-library-item-delta="0"]', '[data-media-library-item-delta="2"]', '.js-media-library-selection'); + + $wrapper = $assert_session->elementExists('css', '.field--name-field-unlimited-media'); + // Remove second item (Bear). + // Order: Cat - Dog. + $wrapper->find('css', "[aria-label='Remove Bear']")->press(); + $this->waitForText('Bear has been removed.'); + $page->pressButton('Save'); + + $assert_session->elementTextContains('css', '.field--name-field-unlimited-media > .field__items > .field__item:last-child', 'Dog'); + } + + /** + * Tests that order is correct after re-order and adding another item. + */ + public function testAddAfterReordering(): void { + $assert_session = $this->assertSession(); + $page = $this->getSession()->getPage(); + + $this->drupalGet('node/add/basic_page'); + $page->fillField('Title', 'My page'); + + $this->openMediaLibraryForField('field_unlimited_media'); + $page->checkField('Select Dog'); + $page->checkField('Select Cat'); + // Order: Dog - Cat. + $this->pressInsertSelected('Added 2 media items.'); + + // Change positions. + // Order: Cat - Dog. + $this->sortableAfter('[data-media-library-item-delta="0"]', '[data-media-library-item-delta="1"]', '.js-media-library-selection'); + + $this->openMediaLibraryForField('field_unlimited_media'); + $this->selectMediaItem(2); + // Order: Cat - Dog - Bear. + $this->pressInsertSelected('Added one media item.'); + + $page->pressButton('Save'); + + $assert_session->elementTextContains('css', '.field--name-field-unlimited-media > .field__items > .field__item:first-child', 'Cat'); + $assert_session->elementTextContains('css', '.field--name-field-unlimited-media > .field__items > .field__item:last-child', 'Bear'); + } + + /** + * {@inheritdoc} + */ + protected function sortableUpdate($item, $from, $to = NULL) { + // See core/modules/media_library/js/media_library.widget.es6.js. + $script = <<<JS +(function ($) { + var selection = document.querySelectorAll('.js-media-library-selection'); + selection.forEach(function (widget) { + $(widget).children().each(function (index, child) { + $(child).find('.js-media-library-item-weight').val(index); + }); + }); +})(jQuery) + +JS; + + $this->getSession()->executeScript($script); + } + } -- GitLab