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. */