diff --git a/core/lib/Drupal/Core/Field/WidgetBase.php b/core/lib/Drupal/Core/Field/WidgetBase.php
index 63671576b2..3536064afc 100644
--- a/core/lib/Drupal/Core/Field/WidgetBase.php
+++ b/core/lib/Drupal/Core/Field/WidgetBase.php
@@ -120,7 +120,7 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface
// Populate the 'array_parents' information in $form_state->get('field')
// after the form is built, so that we catch changes in the form structure
// performed in alter() hooks.
- $elements['#after_build'][] = [get_class($this), 'afterBuild'];
+ $elements['#after_build'][] = [static::class, 'afterBuild'];
$elements['#field_name'] = $field_name;
$elements['#field_parents'] = $parents;
// Enforce the structure of submitted values.
@@ -156,12 +156,12 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface
$field_name = $this->fieldDefinition->getName();
$cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
$parents = $form['#parents'];
+ $field_state = static::getWidgetState($parents, $field_name, $form_state);
// Determine the number of widgets to display.
switch ($cardinality) {
case FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED:
- $field_state = static::getWidgetState($parents, $field_name, $form_state);
- $max = $field_state['items_count'];
+ $max = $field_state['items_count'] - 1;
$is_multiple = TRUE;
break;
@@ -173,6 +173,8 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface
$title = $this->fieldDefinition->getLabel();
$description = FieldFilteredMarkup::create(\Drupal::token()->replace($this->fieldDefinition->getDescription()));
+ $id_prefix = implode('-', array_merge($parents, [$field_name]));
+ $wrapper_id = Html::cleanCssIdentifier($id_prefix . '-add-wrapper');
$elements = [];
@@ -215,45 +217,64 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface
'#default_value' => $items[$delta]->_weight ?: $delta,
'#weight' => 100,
];
+ $element['actions'] = [
+ '#type' => 'actions',
+ 'remove_button' => [
+ '#delta' => $delta,
+ '#name' => implode('_', array_merge($field_state['array_parents'], [$field_name, $delta])) . '_remove_button',
+ '#type' => 'submit',
+ '#value' => t('Remove'),
+ '#validate' => [],
+ '#submit' => [[static::class, 'submitRemove']],
+ '#limit_validation_errors' => [],
+ '#attributes' => [
+ 'class' => ['remove-field-delta--' . $delta],
+ ],
+ '#ajax' => [
+ 'callback' => [static::class, 'removeAjaxContentRefresh'],
+ 'wrapper' => $wrapper_id,
+ 'effect' => 'fade',
+ ],
+ ],
+ ];
}
$elements[$delta] = $element;
}
}
- if ($elements) {
- $elements += [
- '#theme' => 'field_multiple_value_form',
- '#field_name' => $field_name,
- '#cardinality' => $cardinality,
- '#cardinality_multiple' => $this->fieldDefinition->getFieldStorageDefinition()->isMultiple(),
- '#required' => $this->fieldDefinition->isRequired(),
- '#title' => $title,
- '#description' => $description,
- '#max_delta' => $max,
+ $elements += [
+ '#theme' => 'field_multiple_value_form',
+ '#field_name' => $field_name,
+ '#cardinality' => $cardinality,
+ '#cardinality_multiple' => $this->fieldDefinition->getFieldStorageDefinition()->isMultiple(),
+ '#required' => $this->fieldDefinition->isRequired(),
+ '#title' => $title,
+ '#description' => $description,
+ '#max_delta' => $max,
+ ];
+
+ // Add 'add more' button, if not working with a programmed form.
+ if ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED && !$form_state->isProgrammed()) {
+
+ $form['#wrapper_id'] = $wrapper_id;
+ $elements['#prefix'] = '
';
+ $elements['#suffix'] = '
';
+
+ $elements['add_more'] = [
+ '#type' => 'submit',
+ '#name' => strtr($id_prefix, '-', '_') . '_add_more',
+ '#value' => $delta > 0 ? t('Add another item') : t('Add item'),
+ '#attributes' => ['class' => ['field-add-more-submit']],
+ '#limit_validation_errors' => [],
+ '#submit' => [[static::class, 'addMoreSubmit']],
+ '#ajax' => [
+ 'callback' => [static::class, 'addMoreAjax'],
+ 'wrapper' => $wrapper_id,
+ 'effect' => 'fade',
+ ],
];
- // Add 'add more' button, if not working with a programmed form.
- if ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED && !$form_state->isProgrammed()) {
- $id_prefix = implode('-', array_merge($parents, [$field_name]));
- $wrapper_id = Html::getUniqueId($id_prefix . '-add-more-wrapper');
- $elements['#prefix'] = '';
- $elements['#suffix'] = '
';
-
- $elements['add_more'] = [
- '#type' => 'submit',
- '#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])],
- '#submit' => [[get_class($this), 'addMoreSubmit']],
- '#ajax' => [
- 'callback' => [get_class($this), 'addMoreAjax'],
- 'wrapper' => $wrapper_id,
- 'effect' => 'fade',
- ],
- ];
- }
}
return $elements;
@@ -320,6 +341,71 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface
return $element;
}
+ /**
+ * Ajax submit callback for the "Remove" button.
+ *
+ * This re-numbers form elements and removes an item.
+ */
+ public static function submitRemove(&$form, FormStateInterface $form_state) {
+ $button = $form_state->getTriggeringElement();
+ $delta = $button['#delta'];
+ $array_parents = array_slice($button['#array_parents'], 0, -4);
+ $old_parents = array_slice($button['#parents'], 0, -3);
+ $parent_element = NestedArray::getValue($form, array_merge($array_parents, ['widget']));
+ $field_name = $parent_element['#field_name'];
+ $parents = $parent_element['#field_parents'];
+ $field_state = static::getWidgetState($parents, $field_name, $form_state);
+ for ($i = $delta; $i < $field_state['items_count']; $i++) {
+ $old_element_widget_parents = array_merge($array_parents, ['widget', $i + 1]);
+ $old_element_parents = array_merge($old_parents, [$i + 1]);
+ $new_element_parents = array_merge($old_parents, [$i]);
+ $moving_element = NestedArray::getValue($form, $old_element_widget_parents);
+ $moving_element_input = NestedArray::getValue($form_state->getUserInput(), $old_element_parents);
+
+ // Tell the element where it's being moved to.
+ $moving_element['#parents'] = $new_element_parents;
+
+ // Move the element around.
+ $user_input = $form_state->getUserInput();
+ NestedArray::setValue($user_input, $moving_element['#parents'], $moving_element_input);
+ $user_input[$field_name] = array_filter(NestedArray::getValue($user_input, $old_parents));
+
+ $form_state->setUserInput($user_input);
+ }
+ unset($parent_element[$delta]);
+ NestedArray::setValue($form, $array_parents, $parent_element);
+
+ if ($field_state['items_count'] > 0) {
+ $field_state['items_count']--;
+ }
+ $key_exists = '';
+ unset($array_parents[1]);
+ $input = NestedArray::getValue($form_state->getUserInput(), $array_parents, $key_exists);
+
+ $weight = -1 * $field_state['items_count'];
+ foreach ($input as $key => $item) {
+ if ($item) {
+ $input[$key]['_weight'] = $weight++;
+ }
+ }
+ $user_input = $form_state->getUserInput();
+ NestedArray::setValue($user_input, $array_parents, $input);
+ $form_state->setUserInput($user_input);
+ static::setWidgetState($parents, $field_name, $form_state, $field_state);
+ $form_state->setRebuild();
+ }
+
+ /**
+ * Ajax refresh callback for the "Remove" button.
+ *
+ * This returns the new page content to replace the page content made obsolete
+ * by the form submission.
+ */
+ public static function removeAjaxContentRefresh(array &$form, FormStateInterface $form_state) {
+ $button = $form_state->getTriggeringElement();
+ return NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -4));
+ }
+
/**
* Generates the form element for a single copy of the widget.
*/