diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 4cc6424..5c902da 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1612,6 +1612,7 @@ function template_preprocess_field_multiple_value_form(&$variables) { 'class' => ['field-label'], ], t('Order', [], ['context' => 'Sort order']), + t('Operations'), ]; $rows = []; @@ -1633,19 +1634,25 @@ function template_preprocess_field_multiple_value_form(&$variables) { foreach ($items as $item) { $item['_weight']['#attributes']['class'] = [$order_class]; - // Remove weight form element from item render array so it can be rendered - // in a separate table column. + // Remove weight and remove form element from item render array so they + // can be rendered in a separate table columns. $delta_element = $item['_weight']; - unset($item['_weight']); + $remove_element = $item['_remove']; + unset($item['_weight'], $item['_remove']); $cells = [ ['data' => '', 'class' => ['field-multiple-drag']], ['data' => $item], ['data' => $delta_element, 'class' => ['delta-order']], + ['data' => $remove_element, 'class' => ['delta-remove']], ]; + $class = ['draggable']; + if (!empty($remove_element['check']['#checked'])) { + $class[] = 'removed'; + } $rows[] = [ 'data' => $cells, - 'class' => ['draggable'], + 'class' => $class, ]; } diff --git a/core/lib/Drupal/Core/Field/WidgetBase.php b/core/lib/Drupal/Core/Field/WidgetBase.php index b1579f2..4294d6c 100644 --- a/core/lib/Drupal/Core/Field/WidgetBase.php +++ b/core/lib/Drupal/Core/Field/WidgetBase.php @@ -202,6 +202,17 @@ protected function formMultipleElements(FieldItemListInterface $items, array &$f '#default_value' => $items[$delta]->_weight ?: $delta, '#weight' => 100, ]; + $element['_remove']['button'] = [ + '#type' => 'button', + '#attributes' => ['class' => ['hidden']], + '#value' => t('Remove'), + '#weight' => 101, + ]; + $element['_remove']['check'] = [ + '#title' => $this->t('Remove'), + '#type' => 'checkbox', + '#weight' => 102, + ]; } $elements[$delta] = $element; @@ -353,6 +364,13 @@ public function extractFormValues(FieldItemListInterface $items, array $form, Fo // Remove the 'value' of the 'add more' button. unset($values['add_more']); + // Filter out removed items. + foreach ($values as $delta => $value) { + if (!empty($value['_remove']['check'])) { + unset($values[$delta]); + } + } + // The original delta, before drag-and-drop reordering, is needed to // route errors to the correct form element. foreach ($values as $delta => &$value) { @@ -375,7 +393,7 @@ public function extractFormValues(FieldItemListInterface $items, array $form, Fo $field_state = static::getWidgetState($form['#parents'], $field_name, $form_state); foreach ($items as $delta => $item) { $field_state['original_deltas'][$delta] = isset($item->_original_delta) ? $item->_original_delta : $delta; - unset($item->_original_delta, $item->_weight); + unset($item->_original_delta, $item->_weight, $item->_remove); } static::setWidgetState($form['#parents'], $field_name, $form_state, $field_state); } diff --git a/core/misc/form.js b/core/misc/form.js index 7ca64fc..06f81d0 100644 --- a/core/misc/form.js +++ b/core/misc/form.js @@ -247,4 +247,49 @@ } }; + /** + * Initialize value remove checkboxes. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches multipleRemove functionality. + */ + Drupal.behaviors.multipleRemove = { + attach: function (context, settings) { + $(context).find('table.field-multiple-table').once('multiple-remove').each(Drupal.multipleRemove); + } + }; + + /** + * Callback used in {@link Drupal.behaviors.multipleRemove}. + */ + Drupal.multipleRemove = function () { + if ($(this).find('td.delta-remove input[type="checkbox"]').length === 0) { + return; + } + + // Keep track of the table, which checkbox is checked and alias the + // settings. + var table = this; + var $table = $(table); + + // For each of the remove buttons within the table. + $table.find('td.delta-remove input[type="submit"]').removeClass('hidden').on('click', function (e) { + // Either add or remove the removed class based on the state of the + // hidden remove checkbox. + + /** + * @this {HTMLElement} + */ + var $checkbox = $(this).closest('td').find('input[type="checkbox"]'); + var state = $checkbox.prop('checked'); + // Toggle hidden checkbox. + $checkbox.prop('checked', !state); + $checkbox.closest('tr').toggleClass('removed', !state); + + return false; + }); + }; + })(jQuery, Drupal, Drupal.debounce); diff --git a/core/themes/classy/css/components/form.css b/core/themes/classy/css/components/form.css index 15b8ec2..8d7f07f 100644 --- a/core/themes/classy/css/components/form.css +++ b/core/themes/classy/css/components/form.css @@ -6,6 +6,13 @@ form .field-multiple-table { margin: 0; } +form .field-multiple-table tr.removed, +form .field-multiple-table .delta-remove .hidden { + display: none; +} +.js form .field-multiple-table .delta-remove .form-type-checkbox { + display: none; +} form .field-multiple-table .field-multiple-drag { width: 30px; padding-right: 0; /*LTR*/