I'm working on creating a custom field that has multiple entry items in the field. The field would look like this
Field 1 - Text
Field 2 - Text
Field 3 - Text

All would be saved into separate columns.

Now my question is with a cardinality of -1 the Add More works great to put the next item to enter but how would one item be removed from the list of them. Ideally a "delete" or "remove" button would be at the end of the item line but I don't see this. I know with a single field emptying the text field "removes" it but this isn't obvious to a use.

It's not unlike the Remove button that appears next to the Image Fields when multiples are allowed.

Is this something available or less of a support request and more of a feature request?

Note: For 7.x there is a contrib module for that: https://www.drupal.org/project/multiple_fields_remove_button
This issue is about adding that functionality to core.

Files: 

Comments

yched’s picture

Category: support » feature

Feature request :-)

rogueturnip’s picture

That's sort of what I figured but wanted to check :)

Similar request but for 6.x CCK
http://drupal.org/node/196421

The image really shows what would be ideal.
http://drupal.org/files/issues/cck-196421-71.remove-button-example.png

danielb’s picture

I support this, I have two modules that appear buggy in some configurations because there is no way to remove one of the multiple values added with 'add another item'.
The modules are select_or_other and blockreference. I have given users, in addition to a regular multiselect box, the ability to create multiple drag-and-drop single select lists, which allows them to change the order of the selections.
However when set to 'required' and 'unlimited', there is #1117916: No way to remove added options.
The image shown in #2 by rogueturnip, showing a removal link on the right, is exactly what I've come here to request.

LarsKramer’s picture

Version: 7.0 » 8.x-dev
FileSize
23.54 KB

Nice idea. Moving to Drupal 8 where feature requests belong.

Attaching an image from Quick Tabs module which also has a remove button to the right of each instance.

rogueturnip’s picture

I like that this is being added to 8.x as a Feature Request but Drupal 7 is pretty early in it's life and ideally this would also appear there too.

I haven't tried this but I'm wondering if a kludge would be a button with javascript linked to it to empty the fields and then hide the row. Wouldn't degrade nicely but it's a thought.

afeijo’s picture

I made that jQuery idea already, rogueturnip

but if you hit the add another value button, the deleted one returns :p

I will improve the jquery code to watch the button and delete the line again

tim.plunkett’s picture

Title: Multiple field removal » Allow for deletion of a single value of a multiple value field
rogueturnip’s picture

What if instead of hiding the deleted row it instead greyed it out and changed the delete button to restore. This would allow for deleting rows and adding rows and then all the changes would be done on save. This way if you made a mistake on the delete you could restore it.

Any chance you could share what you've done with jquery so far?

tim.plunkett’s picture

#8 is a second feature request on top of this one.

rogueturnip’s picture

I guess it would be. Maybe more of an enhancement to the initial feature request.

What I was looking for was if afeigo had any jquery code/module code he's written that he could share to get this feature in place without waiting for core changes.

rogueturnip’s picture

Maybe someone with more jquery and multivalue form understanding could tell me if this might work.

1. use js (through hook_form_alter?) to add a delete/restore button at the end of each element. use $form['#attached']?
2. if deleting, set disabled=disabled (grey it out), if restoring remove disabled
3. if deleting add a class of deleted
4. use hook_form_alter to add a new submit function that clears all the values for the elements with class=deleted

Because of my lack of deep understanding of mutlivalue elements I don't know if when a add item is clicked if any of this would even execute or if there would be some way to attach the action to the add item.

My guess is this might be able to be done in a contributed module for Drupal 7.

yched’s picture

There was a similar debate for the CCK D6 multigroup feature.
What got implemented over there (done in parallel and independently of the D7 Field API work) was a "delete" button that let you "hide / fold" the corresponding row (or "unhide / unfold" it).

Fidelix’s picture

Subscribing... This is much needed.

davidcie’s picture

Subscribing!

Refineo’s picture

subscribing

MathRo’s picture

sub

jibize’s picture

Yes Sub!

charlie-s’s picture

sub

odegard’s picture

I've solved this in probably a very unsexy manner, but it works.

I've got a multiple field in a content type called TYPE. The field is called 'field_extrafield' and is a term reference field in my case.

Create a module called HOOK with this function:

function HOOK_node_validate($node, $form, &$form_state) {
  if ($node->form_id == 'TYPE_node_form') {
    foreach ($node->field_extrafield['und'] as $mkey => $mvalue) {
      if (is_array($mvalue)) {
        if (!isset($mvalue['tid'])) {
          form_set_value($form['field_extrafield']['und'][$mkey], NULL, $form_state);
        }
      }
    }
  }
}

This might not work out of the box. Mu case inparticular involves field collections so the arrays look different, but this should get you going.

This function add custom validation to the form after it is submitted. The first IF assures you you're working on the correct form. Then we're checking if a value has been set in the multiple field. The fields will be listed as numbered arrays under ['field_name']['und'], but there will also be string items here, so you need to check that you're working on items that are arrays (is_array). Since this is a term ref in my case, I'm checking to see if the array under ['und'] has the ['tid'] set. If not, I set the whole array to NULL using the form_set_value function. This is the only way to write changes to the form inside the validation function.

This feels like a very brutal way of doing it... setting fields to NULL in the validation form... any other suggestions?

RaF7’s picture

subscribe

odegard’s picture

1) It would probably be better to unset the field rather than set it to NULL.

2) In views, you can rearrange the fields you've selected, but also delete them. MerlinofChaos has got this figured out. I suggest looking at his code for inspiration. I won't have time myself for a couple of weeks.

emergencyofstate’s picture

+1 for a Drupal 7 solution for this. Is #19 in the running? Anyone else use it successfully?

daggerhart’s picture

Drupal 7 custom solution.

I just posted in another thread my custom module that can create a remove button for selected 'unlimited' fields in Drupal 7.

http://drupal.org/node/1143880#comment-5198566

jschrab’s picture

Will this work with image/file fields? Will the files get deleted or will there be dangling "fid's"?

daggerhart’s picture

To my knowledge image and file fields have their own Remove button in D7. I specifically added a configuration page to this module where you select which fields to apply the new Remove button in order to prevent complications with fields that already have this functionality.

I just tested Image and File fields on my D7 site, and they do already have a Remove button.

But to answer your question, no this module does not remove or delete any files or images. I've looked into how the File field removes fields using legitimate drupal ajax, and plan to contribute a module that does this correctly, but have not had the time to implement it yet.

Also, I found a tiny bug in the module and have fixed it. Everything should be working fine now.
Here is a link to my blog post where you can download the module:
http://websmiths.co/blog/drupal-7-unlimited-fields-remove-button

Any suggestions are appreciated.

edit: changed link to go to blog post, so I don't have to find all these links and change them after every update.

JordanMagnuson’s picture

Thanks for the custom module daggerhart!

Unfortunately it's not working for me :( . I have an unlimited node_reference field (references project), and it shows up on your module's admin page, but checking it doesn't change anything when I go to edit a node that uses that field... no remove item.

daggerhart’s picture

Hi davidlerin,

I've updated the module to work with date fields, node & user references. The link above now goes to the updated module, but here it is again in case there is some sort of trouble.

v0.3
http://websmiths.co/sites/default/files/projects/unlimitedfield_remove-7...

jschrab’s picture

Is this an issue anymore with merlinofchaos's patch?
http://drupal.org/node/1234574#comment-5232728

daggerhart’s picture

If I understand it correctly, his patch applies to the field collections module. This issue is about core functionality of a remove/delete button for any type of field. Am I mistaken in this?

dww’s picture

@daggerhart: #29 is correct.

To that end, any chance you can put your D7 contrib module here on drupal.org (even if it's just a sandbox project for now) so that discussion and support of your custom solution can continue in a more appropriate channel and we can leave this issue for solving this problem in D8 core?

Thanks!
-Derek

daggerhart’s picture

@dww,

Absolutely.

I have created the sandbox project for this functionality as a D7 module here:
http://drupal.org/sandbox/daggerhart/1363702

Sorry to hijack the thread.
Thanks,
- Jonathan

arrrgh’s picture

@daggerhart just downloaded your sandbox (version 7.x-0.3)

Thanks so much for posting this online and it's a great solution to an infuriating problem in D7. Should be in Core IMHO.

Thanks again - very grateful.

Rob

xjm’s picture

Issue tags: +Usability

Tagging.

Albert Volkman’s picture

Just checked out and used daggerhart's module. Worked perfectly. Thank you!

daggerhart++

bxCreative’s picture

@daggerhart - works perfectly!! Thanks for the save :)

druvision’s picture

The 'UnlimitedField Remove' module works great, but not for link fields. On link fields, the button doesn't appear at all.

Use-case: If the link field title is mandatory, I can't remove it at all (which is why I needed the 'remove' feature on the 1st place). By making the title optional, I can delete it in the usuall, non-intuitive D7 method, by simply deleting the text inside the field. But then I can't make it mandatory.

Ref: http://drupal.org/node/1616310

colinmcclure’s picture

@daggerhart - works very well even inside field_collection with my custom multi-value field. There is just one niggle I found when testing it out...

If you have a field_collection set to cardinality -1 and you remove a field_collection container item with its Remove button then the remove button provided by module ceases to work until refresh.

Despite this little niggle and very specific use case, it works a treat and saved me a heap of time! Thank-you!

dremy’s picture

Has anyone attempted this solution with Addressfield? Currently on node save, it adds the empty Addressfield and plots a point in the middle of the US in addition to the address submitted.

swentel’s picture

Assigned: Unassigned » swentel

Marked #1120162: Add "delete" links to multivalue field values as duplicate. Assigning to myself, going to try this during the weekend.

swentel’s picture

Assigned: swentel » Unassigned

Ok, this is too crazy to get it right before 1 december.

Fidelix’s picture

swentel, apparently there is more time to get this done:
http://buytaert.net/drupal-8-feature-freeze-extended

swentel’s picture

Yeah, but I'm not sure whether I have time enough for an initial patch before 1 december to get it going enough :)

jibran’s picture

Is it still a feature request or can we convert it to task? It is very basic functionality missing form core so IMO we can change it to task.

jibran’s picture

Category: feature » task

Just confirmed with @swentel on IRC he also thinks it is a difficult task.

swentel’s picture

Version: 8.x-dev » 9.x-dev

This will be D9 material I'm afraid unless someone gets on this.

rafamd’s picture

Version: 9.x-dev » 8.x-dev

Well .. given it's a normal priority task .. it wouldn't hurt in the 8.x queue for now?

FWIW, +1 to this fix, good day you all! :)

EDIT: oops, didn't mean to change version state, leaving it now, but please swentel or any other, adjust it as you see best.

ivanjaros’s picture

I'd quite welcome this. In D7 I always had to create the remove button manually and this I think should be present in core.

ivanjaros’s picture

BTW: additionaly I consider this quite important because if you have many values in a field and you manually remove the value, since there is no remove button, and you'll add another values you'll end up with empty fields and from UX POV it just looks bad.

ivanjaros’s picture

Sheesh 2 years and 11 months old issue.

Oh and I wanted to mention that this is a bit tricky only because fields are keyed by integers. If keys are changed to strings it's a piece of cake to programm the remove button functionality.

geerlingguy’s picture

Subscribing and adding that yes, this is a bit of a tricky problem to solve. There are few fields that implement a remove button (e.g. File/image fields and a few other custom fields), and it's hard (and annoying) to build in a 'Remove' button on custom multi-value fields, but it would be a huge UX win if we had this out of the box for multi-value fields.

I'm coming over from #2035753: Add 'remove item' function to multi-value autocomplete widget.

Note also that there's a contrib module that does what would be nice to do in core for multi-value fields: Multiple Fields Remove Button

paulwdru’s picture

However, Multiple Fields Remove Button does NOT support all modules / widgets, each needs to be separately programmed.

Hopefully, Remove button is added to all multivalue fields in core for all modules / widgets

paulwdru’s picture

Hi,

I found a solution, with reference to #2287803: Make this work with all fields by default, apply Patch #4 on multiple_fields_remove_button-7.x-1.4.zip shall work with all modules and widgets.

Please take note that multiple_fields_remove_button-7.x-1.5.zip does NOT have the patch #4 committed, also not patchable with patch #4, already reported to the maintainer.

Drupal 8 shall make this in core coz it's ridiculous without a Remove button for Multivalue fields since day one.

Thanks

Artusamak’s picture

Version: 8.0.x-dev » 8.1.x-dev

Will not happen in 8.0.x-dev since it's not critical.

paulwdru’s picture

Hi,

It's Absolutely Critical if your page has multivalue Field Collection, File, Image fields which already provided Remove button, the whole page will become Very Messy and NOT Standardized if some multivalue fields have Remove button while others do not.

Remove button shall be made in core so as to provide a user-friendly, clean and standardized interface. #2287803: Make this work with all fields by default

Thanks

jojonaloha’s picture

@paulwdru, I think @ARtusamak meant that it is not Critical based on the Priority levels of issues and because of that cannot go into 8.0.x since it does not qualify as an allowed change for patch releases. It looks like there is still a little time to get this in to 8.1.x, so a patch would be helpful.

jusfeel’s picture

it's not working with editable display formatter so you won't clear the value through ajax save.

As always, great software needs a "working" UI or it's just for small group of geeks.

paulwdru’s picture

@jusfeel,

Drupal is always meant for Geeks, powerful & highly flexible, I love it.

The only drawbacks of Drupal are confusing / unorganized / inconsistent User-Interface, not nicely touched up in the perspective of end users' experiences.

axel.rutz’s picture

Issue summary: View changes

Added to summary:

Note: For 7.x there is a contrib module for that: https://www.drupal.org/project/multiple_fields_remove_button
This issue is about adding that functionality to core.

Eyal Shalev’s picture

I created a working remove item button in a custom field widget using the following code:



  /**
   * {@inheritdoc}
   */
  protected function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) {
    $elements = parent::formMultipleElements($items,$form,$form_state);
    $field_name = $this->fieldDefinition->getName();
    $parents = $form['#parents'];

    foreach (Element::children($elements) as $element_key) {
      if (is_int($element_key)) {
        $delta = $element_key;

        $wrapper_array = array_merge($parents, [
          $field_name,
          $delta,
          'remove',
          'item'
        ]);
        $wrapper_id = Html::getUniqueId(implode('-', $wrapper_array));
        $elements[$delta]['#prefix'] = "
"; $elements[$delta]['#suffix'] = '
'; $elements[$delta]['remove_item'] = [ '#type' => 'submit', '#value' => $this->t('Remove item'), '#submit' => [[$this, 'removeItemSubmit']], '#ajax' => [ 'callback' => [$this, 'removeItemAjax'], 'wrapper' => $wrapper_id, 'effect' => 'fade', ], '#name' => implode('_', $wrapper_array), '#attributes' => ['class' => ['field-remove-item-submit']], '#limit_validation_errors' => [array_merge($parents, [$field_name])], ]; } } return $elements; } public function removeItemSubmit(array $form, FormStateInterface $form_state) { $button = $form_state->getTriggeringElement(); // Go one level up in the form, to the widgets container. $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1)); $container_element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -2)); $field_name = $this->fieldDefinition->getName(); $field_parents = $element['#field_parents']; $delta = $element['#delta']; $field_values = &$form_state->getValue($container_element['#parents']); unset($field_values[$delta]); // Increment the items count. $field_state = static::getWidgetState($field_parents, $field_name, $form_state); $field_state['items_count']--; static::setWidgetState($field_parents, $field_name, $form_state, $field_state); $form_state->setRebuild(); } public function removeItemAjax(array $form, FormStateInterface $form_state) { return [ '#type' => 'hidden', '#attributes' => [ 'data-description' => 'It is a requirement to return something to make an ajax call hide the entire element.' ] ]; }
Eyal Shalev’s picture

axel.rutz’s picture

Status: Active » Needs review

Cool! so needs-review...

Status: Needs review » Needs work

The last submitted patch, 60: 1038316-remove-item.patch, failed testing.

mayurjadhav’s picture

Status: Needs work » Needs review
FileSize
5.84 KB

@Eyal Shalev file is present but your patch didn't applied, let me try this patch.
If it gives the same result then i think bot Test has some issues, we need to work on them too.

axel.rutz’s picture

This would be easier to review if the (totally sensible) code style changes were not in it.

Eyal Shalev’s picture

I tried to replace all the [] arrays to array(). This was done manually so it could fail.

Status: Needs review » Needs work

The last submitted patch, 65: single-value-deletion-1038316-65.patch, failed testing.

mayurjadhav’s picture

Status: Needs work » Needs review

Marking as Needs Review for #63 patch.

axel.rutz’s picture

Code looks good, but seems to need a rebase.

mayurjadhav’s picture

I could apply the patch, so no reroll/rebase is needed.

axel.rutz’s picture

See the testbot in #65:

8.1.x: PHP 5.5 & MySQL 5.5 Patch failed to apply

mayurjadhav’s picture

@alex.rutz, Yes #65 patch failed to apply and bot test too. I was talking about #63.

Eyal Shalev’s picture

I've found some bugs with the first patch.

The changes are:

  • The ajax callback no longer return an empty element but the whole container.
  • The id_prefix and wrapper_id variables are moved up in the formMultipleElements method.
  • The submit method will now affect both the form_state values and the form_state user_input.
  • The submit method will now only unset the choosen item but move all the items after it one delta lower.

Status: Needs review » Needs work

The last submitted patch, 72: single-value-deletion-1038316-72.patch, failed testing.

Eyal Shalev’s picture

Status: Needs work » Needs review
FileSize
958 bytes
  • Fixed the cause of the test fail.
  • Only add the remove button when the field cardinality is unlimited and it is not programmed.

Status: Needs review » Needs work

The last submitted patch, 74: single-value-deletion-1038316-74.patch, failed testing.

Eyal Shalev’s picture

Status: Needs work » Needs review
FileSize
4.15 KB

Same as #74. I didn't patch against 8.1.x

szato’s picture

Tested, works for me.
One comment:
Removing the last item removes the "Add another item" button too (because the whole wrapper is re-generated by ajax with no item).
My solution:
added checking:
$field_state['items_count'] > 0
to avoid deletion (displaying the remove button) when only one item is left.

Status: Needs review » Needs work

The last submitted patch, 77: single-value-deletion-1038316-77.patch, failed testing.

szato’s picture

Status: Needs work » Needs review

Re-test passed.

dropdiver’s picture

This is not working in autocomplete term fields, i get an ajax-http Error!

apraturu’s picture

I am having an issue related to removing single items from a list on an edit form, both before and after installing patch #77. I'm wondering if anyone has had a similar issue. It happens when I do the following:

1) Create a list of 3 items (A, B, and C) and save the list.

2) Open the list for editing. As expected, the list appears on the edit form like this:
1. A
2. B
3. C
4.

3) Remove item A and then item B. The list now appears like this, as it should:
1. C
2.

4) Click the button to add another item to the list.

The expected result is this list:
1. C
2.
3.

However, the actual result is this:
1. C
2.
3. C

The new item comes pre-populated with the item that was in that slot at the start, rather than an additional empty slot as expected. Any ideas on the cause/or solution to this would be extremely helpful. Thanks!

Edit: You can reproduce this behavior on simplytest.me with a clean Drupal 8.1.8 install. Just add a new field to an article content type with unlimited values and follow the steps above and you'll see the same result.

bleen’s picture

FileSize
151.17 KB

I figured out the cause of the issue in #81 though I have no idea what the correct fix might be... Here's the problem:

During the course of processing the form, the FormBuilder->rebuildForm() method is called. It, in turn, eventually calls a stack that looks like this:

If you take a close look at EntityFormDisplay->buildForm() you see this:

public function buildForm(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state) {
    // ... do some things

    foreach ($this->getComponents() as $name => $options) {
      if ($widget = $this->getRenderer($name)) {
        $items = $entity->get($name);  // <==== HERE"S YOUR PROBLEM
       // .... do more things
      }
    }
    // ... do more things
  }

The problem is that $items gets set based on the cached $entity object without taking into account the changes that have already been made to the form as reflected in $form_state.

A quick-and-hackey fix would be to let this happen and then after the fact, adjust $items based on $form_state , but this seems both wasteful and fragile.

I think this is a core bug regardless and should maybe (probably?) be addressed outside of this issue, but I'm not sure. A LOT of dominos have to fall in just the right way, but this could lead to an editor inadvertently overwriting his/her own data if their site has code to manipulate the entity edit form the way that the functionality in this issue would if it ever gets in.

dj1999’s picture

#77 patch working my fine except comment #80.

Regarding #80 prepare a patch. Please review it.

Status: Needs review » Needs work

The last submitted patch, 83: single-value-deletion-1038316-83.patch, failed testing.

dj1999’s picture

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

bleen’s picture

Status: Needs work » Needs review

Here testbot... Here boy

mpotter’s picture

One problem with this patch is that the removeItemSubmit public function added by this patch is incompatible with the same function name used in the entity_browser module (entity_browser/src/Plugin/Field/FieldWidget/EntityReferenceBrowserWidget.php)

I suggest using a different name here so it doesn't break existing sites.

Or, if entity_browser needs to update it's argument list, let me know and I'll submit that as an issue for them, but wasn't sure they would care about a conflict with a patch that hasn't been committed yet.

bleen’s picture

Status: Needs review » Needs work

The patch in #85 has some issues...

First, the "remove" button does nothing at all in the following circumstance:

  • Add a text field to a content type with unlimited cardinality
  • On the field setting form set the default values to "red" "white" and "blue" and save the field
  • Now edit the field
  • Notice that the "remove" button added to the default value fields doesn't do anything on that form

Another issue (I'm 99% sure this is realted to #81/#82:

  • Add a text field with an unlimited cardinality to a content type
  • create a new node with "red" "white" and "blue" as values in that field (save the node)
  • Now edit the node.
  • Click the remove button next to "blue". You should now have "red" "white" and empty field but instead you have "red" blue" and empty field.
smiletrl’s picture

I was working on this issue these days, and this is one widget working for my string text field. It uses some code from field_collection module, adds a few customization. Maybe someone can get some idea for your customization.


namespace Drupal\bix_global\Plugin\Field\FieldWidget;

use Drupal\Component\Utility\Html;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Field\Plugin\Field\FieldWidget\StringTextfieldWidget;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Field\FieldStorageDefinitionInterface;

/**
 * Plugin implementation of the custom string widget.
 *
 * @FieldWidget(
 *   id = "bix_global_string_textfield",
 *   label = @Translation("BIX Global Textfield"),
 *   field_types = {
 *     "string"
 *   },
 * )
 */
class BixGlobalStringTextfieldWidget extends StringTextfieldWidget {

  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    $element = parent::formElement($items, $delta, $element, $form, $form_state);


    $field_name = $this->fieldDefinition->getName();

    // Nest the field collection item entity form in a dedicated parent space,
    // by appending [field_name, delta] to the current parent space.
    // That way the form values of the field collection item are separated.
    $parents = array_merge($element['#field_parents'], [$field_name, $delta]);
    //$parents = $form['#parents'];
    $element += [
      //'#element_validate' => [[static::class, 'validate']],
      '#parents' => $parents,
      '#field_name' => $field_name,
    ];

    // Put the remove button on unlimited cardinality field collection fields.
    if ($this->fieldDefinition->getFieldStorageDefinition()->getCardinality() == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
      $options = ['query' => ['element_parents' => implode('/', $element['#parents'])]];
      $element['actions'] = [
        '#type' => 'actions',
        'remove_button' => [
          '#delta' => $delta,
          '#name' => implode('_', $parents) . '_remove_button',
          '#type' => 'submit',
          '#value' => t('Remove'),
          '#validate' => [],
          '#submit' => [[static::class, 'removeSubmit']],
          '#limit_validation_errors' => [],
          '#attributes' => array(
            'class' => array('bix-global-custom-multi-field-remove-button'),
          ),
          '#ajax' => [
            'callback' => [$this, 'ajaxRemove'],
            'options' => $options,
            'effect' => 'fade',
            'wrapper' => $form['#wrapper_id'],
          ],
          '#weight' => 1000,
        ],
      ];
    }
    return $element;
  }

  /**
   * {@inheritdoc}
   */
  protected function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) {
    // We don't want to render empty items on field collection fields
    // unless a) the field collection is empty ; b) the form is rebuilding,
    // which means that the user clicked on "Add another item"; or
    // c) we are creating a new entity.
    if ((count($items) > 0) && !$form_state->isRebuilding() && !$items->getEntity()->isNew()) {
      $field_name = $this->fieldDefinition->getName();
      $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
      $parents = $form['#parents'];
      if ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
        $field_state = static::getWidgetState($parents, $field_name, $form_state);
        $field_state['items_count']--;
        static::setWidgetState($parents, $field_name, $form_state, $field_state);
      }
    }

    // Adjust wrapper identifiers as they are shared between parents and
    // children in nested field collections.
    $form['#wrapper_id'] = Html::getUniqueID($items->getName());
    $elements = parent::formMultipleElements($items, $form, $form_state);
    $elements['#prefix'] = '<div id="' . $form['#wrapper_id'] . '">';
    $elements['#suffix'] = '</div>';
    $elements['add_more']['#ajax']['wrapper'] = $form['#wrapper_id'];
    return $elements;
  }

  /**
   * Submit callback to remove an item from the field UI multiple wrapper.
   *
   * When a remove button is submitted, we need to find the item that it
   * referenced and delete it. Since field UI has the deltas as a straight
   * unbroken array key, we have to renumber everything down. Since we do this
   * we *also* need to move all the deltas around in the $form_state->values
   * and $form_state input so that user changed values follow. This is a bit
   * of a complicated process.
   */
  public static function removeSubmit($form, FormStateInterface $form_state) {
    $button = $form_state->getTriggeringElement();
    $delta = $button['#delta'];

    // Where in the form we'll find the parent element.
    $address = array_slice($button['#array_parents'], 0, -4);
    $address_state = array_slice($button['#parents'], 0, -3);

    // Go one level up in the form, to the widgets container.
    $parent_element = NestedArray::getValue($form, array_merge($address, ['widget']));

    $field_name = $parent_element['#field_name'];
    $parents = $parent_element['#field_parents'];

    $field_state = static::getWidgetState($parents, $field_name, $form_state);

    // Go ahead and renumber everything from our delta to the last
    // item down one. This will overwrite the item being removed.
    for ($i = $delta; $i <= $field_state['items_count']; $i++) {
      $old_element_address = array_merge($address, ['widget', $i + 1]);
      $old_element_state_address = array_merge($address_state, [$i + 1]);
      $new_element_state_address = array_merge($address_state, [$i]);

      $moving_element = NestedArray::getValue($form, $old_element_address);

      $moving_element_value = NestedArray::getValue($form_state->getValues(), $old_element_state_address);

      $moving_element_input = NestedArray::getValue($form_state->getUserInput(), $old_element_state_address);

      // Tell the element where it's being moved to.
      $moving_element['#parents'] = $new_element_state_address;

      // Move the element around.
      $form_state->setValueForElement($moving_element, $moving_element_value);
      $user_input = $form_state->getUserInput();
      NestedArray::setValue($user_input, $moving_element['#parents'], $moving_element_input);
      $form_state->setUserInput($user_input);

      // Move the entity in our saved state.
      if (isset($field_state['entity'][$i + 1])) {
        $field_state['entity'][$i] = $field_state['entity'][$i + 1];
      }
      else {
        unset($field_state['entity'][$i]);
      }
    }

    // Then remove the last item. But we must not go negative.
    if ($field_state['items_count'] > 0) {
      $field_state['items_count']--;
    }
    else {
      $form_state->setValue('empty_item', 1);
    }

    // Fix the weights. Field UI lets the weights be in a range of
    // (-1 * item_count) to (item_count). This means that when we remove one,
    // the range shrinks; weights outside of that range then get set to
    // the first item in the select by the browser, floating them to the top.
    // We use a brute force method because we lost weights on both ends
    // and if the user has moved things around, we have to cascade because
    // if I have items weight weights 3 and 4, and I change 4 to 3 but leave
    // the 3, the order of the two 3s now is undefined and may not match what
    // the user had selected.
    $input = NestedArray::getValue($form_state->getUserInput(), $address_state);
    // Sort by weight.
    // This sort function depends on field_collection module.
    uasort($input, '_field_collection_sort_items_helper');

    // Reweight everything in the correct order.
    $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, $address_state, $input);
    $form_state->setUserInput($user_input);

    static::setWidgetState($parents, $field_name, $form_state, $field_state);

    $form_state->setRebuild();
  }

  /**
   * Ajax callback to remove a field collection from a multi-valued field.
   *
   * @param array $form
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *
   * @return \Drupal\Core\Ajax\AjaxResponse
   *   An AjaxResponse object.
   *
   * @see self::removeSubmit()
   */
  function ajaxRemove(array $form, FormStateInterface &$form_state) {
    // At this point, $this->removeSubmit() removed the element so we just need
    // to return the parent element.
    $button = $form_state->getTriggeringElement();
    $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -3));
    $delta = $element['#max_delta'];

    if ($delta === 0) {
      $empty_item_flag = $form_state->getValue('empty_item');
      // Reset the last item's value to be NULL.
      if ($empty_item_flag) {
        $element[$delta]['value']['#value'] = '';
      }
    }
    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public static function addMoreSubmit(array $form, FormStateInterface $form_state) {
    $button = $form_state->getTriggeringElement();

    // Go one level up in the form, to the widgets container.
    $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
    $field_name = $element['#field_name'];
    $parents = $element['#field_parents'];

    // Increment the items count.
    $field_state = static::getWidgetState($parents, $field_name, $form_state);
    $field_state['items_count']++;

    static::setWidgetState($parents, $field_name, $form_state, $field_state);

    $form_state->setRebuild();
  }

  /**
   * {@inheritdoc}
   */
  public static function addMoreAjax(array $form, FormStateInterface $form_state) {
    $element = parent::addMoreAjax($form, $form_state);

    // Set the newest item empty value.
    $delta = $element['#max_delta'];
    $element[$delta]['value']['#default_value'] = '';
    $element[$delta]['value']['#value'] = '';

    return $element;
  }
}

mpotter’s picture

smiletrl: rather than pasting a huge amount of text like that, it would probably be better to upload it as an actual file.

bleen: I can confirm both of the issues you reported, so agree this needs more work.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

tacituseu’s picture

How about something simpler ?
Adding column and a checkbox, that is used in WidgetBase::extractFormValues() to filter out unwanted values.
The advantage is that it avoids AJAX callbacks, also you can change your mind and just un-check.
Could also add JS/behavior for it that hides the checkboxes and adds buttons in their place (that just toggle the hidden checkboxes).

1038316-93-field-multiple-remove-item-alt.png

Status: Needs review » Needs work

The last submitted patch, 93: 1038316-93-field-multiple-remove-item-alt.patch, failed testing.

tacituseu’s picture

Status: Needs work » Needs review
FileSize
3.45 KB
Munavijayalakshmi’s picture

Assigned: Unassigned » Munavijayalakshmi
Status: Needs review » Needs work
+++ b/core/lib/Drupal/Core/Field/WidgetBase.php
@@ -353,6 +369,11 @@ public function extractFormValues(FieldItemListInterface $items, array $form, Fo
+        // Filter out removed items

Comments should (noramlly) begin with a capital letter and end with a full stop / period .

+++ b/core/lib/Drupal/Core/Field/WidgetBase.php
@@ -202,6 +202,22 @@ protected function formMultipleElements(FieldItemListInterface $items, array &$f
+          $element['_remove'] = array(
+            '#title' => $this->t('Remove'),
+            '#title_display' => 'invisible',
+            '#type' => 'checkbox',
+            '#default_value' => $items[$delta]->_remove ?: FALSE,
+            '#weight' => 101,
+          );

use short array syntax (new coding standard).

Munavijayalakshmi’s picture

Assigned: Munavijayalakshmi » Unassigned
Status: Needs work » Needs review
FileSize
3.44 KB
942 bytes
rgpublic’s picture

Hm. Don't know if I'm mistaken but patch #97 seems to add the checkbox to every field - no matter whether it actually consists of multiple values (cardinality!=1) or not.

tacituseu’s picture

@rgpublic: it is added inside if ($is_multiple) {...} right beside '_weight' so it really shouldn't

rgpublic’s picture

Ah, thanks @tacituseu. See it now. Did a mistake while adapting the patch to 8.2. It seems to work now. I've attached it in case anyone needs this.

Status: Needs review » Needs work

The last submitted patch, 100: field_multiple_remove_item_alt-1038316-100.patch, failed testing.

tacituseu’s picture

Adds class 'removed' to the row so it could be themed or just "display: none"d.

Status: Needs review » Needs work

The last submitted patch, 102: 1038316-102-field-multiple-remove-item-alt.patch, failed testing.

tacituseu’s picture

tacituseu’s picture

GRO’s picture

Re-roll of patch from #105 for 8.2.x

bleen’s picture

Can you please comment on whether the latest patch addresses #81/82 and #89

tacituseu’s picture

@bleen: yes the alternative approach solves all of them by not messing with $form_state over AJAX.

bleen’s picture

I had a chance to test the patch in #106 and I can confirm that it does address #81/#82 as well as #89.

In addition I was not able to find any other problems when testing with textfields that have unlimited cardinality.

thumbs up from me

swentel’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests

The patch from 106 is hard to scan with the unrelated coding standards updates .. can we leave those out please. Also, we definitiely need for tests for this.

tacituseu’s picture

Also needs work for fields with fixed (>1) cardinality, and no-js graceful degradation.

GRO’s picture

Re-rolled patch from #106, removed coding standards updates and added a check of field items to only apply the remove button when the field is not a new/blank field item.

tacituseu’s picture

No-js graceful degradation and coding standard fixes from #112, left out "apply the remove button when the field is not a new/blank field item" as it forces you to "Add another item" to be able to delete last one, it makes sense in file/image widgets due to their nature but not on generic fields in my opinion.
Still no tests,

tacituseu’s picture

Status: Needs work » Needs review
tacituseu’s picture

Minor CSS cleanup, swapping order of button and checkbox to get styles consistent with file/image widget.

tacituseu’s picture