Problem/Motivation

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.

Copied from #93447: Deleting the first value in a required, multivalue field fails validation

Proposed resolution

Use ajax and reload element to remove the item from the page.

Remaining tasks

  • Decide on direction
  • Add tests
  • Review
  • Commit

User interface changes

API changes

Data model changes

CommentFileSizeAuthor
#285 Screenshot 2024-01-18 at 9.03.30 AM.png120.05 KBnicole.harnish
#285 Screenshot 2024-01-18 at 9.03.17 AM.png124.62 KBnicole.harnish
#207 screenshot-schoolpwa-2019.11.24-10_40_42.png9.75 KBMrPaulDriver
#207 screenshot-stm5dda5503bc6b0-a6ker7kr3mv4ghopsmpunkgqfe9vxdnw.tugboat.qa-2019.11.24-10_57_46.png122.11 KBMrPaulDriver
#204 interdiff_203-204.txt1.34 KBsanfair
#204 1038316-204.patch8.34 KBsanfair
#203 interdiff_196-203.txt5.1 KBsanfair
#203 1038316-203.patch8.18 KBsanfair
#197 1038316-196.patch9.14 KBbartzaalberg
#196 Screenshot buttons dialog.png89.36 KBTess Bakker
#195 1038316-195.patch9.13 KBSpadXIII
#194 1038316-194.patch9.13 KBSpadXIII
#188 1038316-188.patch9.13 KBcchoe1
#183 1038316-183.patch8.73 KBcchoe1
#182 1038316-182.patch8.95 KBcchoe1
#179 1038316-179.patch8.65 KBheddn
#179 interdiff_173-179.txt2.16 KBheddn
#173 1038316-171-173-interdiff.txt1.33 KBjan.stoeckler
#173 1038316-173.patch8.1 KBjan.stoeckler
#171 1038316-171.patch7.73 KBjan.stoeckler
#166 interdiff_152-166.txt4.28 KBheddn
#166 1038316-166.patch8.1 KBheddn
#158 interdiff_152-158.txt2.85 KBheddn
#158 1038316-158.patch8.49 KBheddn
#155 Screenshot_2018-01-15_15-42-05.png3.24 KBheddn
#155 Screenshot_2018-01-15_15-39-37.png25.12 KBheddn
#153 interdiff_151-152.txt9.09 KBheddn
#152 1038316-152.patch7.62 KBheddn
#151 1038316-151.patch5.19 KBheddn
#145 1038316-145-field-multiple-remove-item-alt.patch7.24 KBtacituseu
#143 allow_single_remove_for_multiple_textfield_core_8.4.x-1038316-143.patch6.1 KBrealityloop
#135 allow_single_remove_for_multiple_textfield_core_8.3.3-1038316-135.patch7.97 KBMRPRAVIN
#133 allow_single_remove_for_multiple_textfield_core_8.3.5-1038316-133.patch6.14 KBsamuel.seide
#131 1038316-131-interdiff.txt2.94 KBtacituseu
#131 1038316-131-field-multiple-remove-item-alt.patch6.89 KBtacituseu
#129 1038316-129-interdiff.txt3.08 KBtacituseu
#129 1038316-129-field-multiple-remove-item-alt.patch6.82 KBtacituseu
#128 allow_single_remove_for_multiple_textfield_core_8.3.3-1038316-128.patch10.84 KBMRPRAVIN
#125 Screen Shot 2017-07-03 at 4.01.48 PM.png30.03 KBsamuel.seide
#125 Screen Shot 2017-07-03 at 3.43.11 PM.png295.23 KBsamuel.seide
#119 allow_for_deletion_of_a-1038316-114.patch6.2 KBvytch
#117 allow_for_deletion_of_a-1038316-113.patch6.78 KBvytch
#115 1038316-113-116-interdiff.txt1.58 KBtacituseu
#115 1038316-116-field-multiple-remove-item-alt.patch5.62 KBtacituseu
#113 1038316-105-113-interdiff.txt3.33 KBtacituseu
#113 1038316-113-field-multiple-remove-item-alt.patch5.66 KBtacituseu
#112 allow_for_deletion_of_a-1038316-112.patch6.16 KBGRO
#106 allow_for_deletion_of_a-1038316-106.patch18.51 KBGRO
#105 1038316-104-106-interdiff.txt4.41 KBtacituseu
#105 1038316-106-field-multiple-remove-item-button-alt.patch5.74 KBtacituseu
#104 1038316-102-104-field-multiple-remove-item-alt.txt786 bytestacituseu
#104 1038316-104-field-multiple-remove-item-alt.patch4.94 KBtacituseu
#102 1038316-97-102-interdiff.txt1.95 KBtacituseu
#102 1038316-102-field-multiple-remove-item-alt.patch4.97 KBtacituseu
#100 field_multiple_remove_item_alt-1038316-100.patch3.51 KBrgpublic
#97 interdiff.txt942 bytesMunavijayalakshmi
#97 field_multiple_remove_item_alt-1038316-97.patch3.44 KBMunavijayalakshmi
#95 1038316-95-field-multiple-remove-item-alt.patch3.45 KBtacituseu
#93 1038316-93-field-multiple-remove-item-alt.patch2.87 KBtacituseu
#93 1038316-93-field-multiple-remove-item-alt.png14.18 KBtacituseu
#85 single-value-deletion-1038316-85.patch4.47 KBdj1999
#83 single-value-deletion-1038316-83.patch1.69 KBdj1999
#82 callstack.png151.17 KBbleen
#77 single-value-deletion-1038316-77.patch4.19 KBszato
#77 interdiff-1038316-76-77.txt670 bytesszato
#76 single-value-deletion-1038316-76.patch4.15 KBEyal Shalev
#74 single-value-deletion-1038316-74.patch958 bytesEyal Shalev
#72 single-value-deletion-1038316-72.patch3.81 KBEyal Shalev
#65 single-value-deletion-1038316-65.patch3.4 KBEyal Shalev
#63 single-value-deletion-1038316-63.patch5.84 KBmayurjadhav
#60 1038316-remove-item.patch2.72 KBEyal Shalev
#4 quicktab_remove_buttons.jpg23.54 KBLarsKramer
#212 1038316-212.patch7.65 KBDenisev
#212 interdiff_204-212.txt936 bytesDenisev
#214 1038316-214.patch8.43 KBelectrokate
#224 1038316-224.patch9.94 KBanmolgoyal74
#225 1038316-225.patch8.54 KBfabianfiorotto
#228 1038316-228.patch8.54 KBankithashetty
#228 interdiff_1038316_225-228.txt789 bytesankithashetty
#230 1038316-229.patch8.45 KBscott.whittaker
#232 1038316-231.patch8.48 KBscott.whittaker
#234 1038316-234.patch7.8 KBLal_
#235 1038316-235.patch7.8 KBanmolgoyal74
#235 interdiff_234_235.txt438 bytesanmolgoyal74
#241 1038316.patch7.94 KBlinhnm
#242 1038316-242.interdiff.txt4.62 KBneclimdul
#242 1038316-242.patch6.09 KBneclimdul
#252 1038316-before_patch-242.mp483.59 KBAbhijith S
#252 1038316-after_patch-242.mp4233.85 KBAbhijith S
#256 1038316-nr-bot.txt1.42 KBneeds-review-queue-bot
#262 1038316-262.patch5.97 KBperry.franken
#263 interdiff_242-262.txt756 bytesperry.franken
#264 interdiff_242-264.txt764 bytesperry.franken
#264 interdiff_242-264.txt764 bytesperry.franken
#265 1038316-264.patch5.55 KBperry.franken
#270 Screenshot 2023-07-05 at 11.17.32 AM.png62.1 KBsmustgrave

Issue fork drupal-1038316

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Support from Acquia helps fund testing for Drupal Acquia logo

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?

rp7’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

robcarr’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.

Anonymous’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.

Anonymous’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.

Anonymous’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.

geek-merlin’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'] = "<div id={$wrapper_id}>";
        $elements[$delta]['#suffix'] = '</div>';
        $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

geek-merlin’s picture

Status: Active » Needs review

Cool! so needs-review...

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.

geek-merlin’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.

mayurjadhav’s picture

Status: Needs work » Needs review

Marking as Needs Review for #63 patch.

geek-merlin’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.

geek-merlin’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.
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.
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.

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.

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

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.

tacituseu’s picture

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

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

vytch’s picture

Adding a fix to #112 to solve a double binding issue

vytch’s picture

Fixing patch

tacituseu’s picture

Could you please stop this nonsense of rolling for 8.3.x/8.2.x ?
It is just pointless noise, there is no way this gets into 8.3.x.

rgpublic’s picture

@tacituseu: But you forget that this might be very helpful for people who currently cannot upgrade and need this.

tacituseu’s picture

@rgpublic: I didn't, frankly they shouldn't use it on production, it's a hard problem, the issue was created in 2011 and is slow enough even without it, the proposed approach doesn't even have blessing of the system maintainers yet.

What would be very helpful is tests ;) they would be useful even if approach changes.

samuel.seide’s picture

I tested the 8.4.x patch on a vanilla install of drupal 8.4.x dev, with the paragraphs and field_group module installed. If you create a field group add a entity reference paragraphs inside the group and give that a default paragraph, you will end up with this error:

screenshot of error

in the code, the error is originating from the code $remove_element = $item['_remove']; as the default paragraph doesn't have a value for $item['_remove'] and so it comes over as null.

Here's how I had the paragraph setup in the content type:

setup of content type

tacituseu’s picture

@samuel.seide: Thanks for testing, it happens because the code assumes (wrongly) that each field extending WidgetBase and overwriting ::formMultipleElements() will call parent::formMultipleElements(), but Paragraphs doesn't.
I will post an updated patch in a couple of days.

I'm not familiar with Paragraphs, is your expectation it will somehow 'work' with it, or just not break ?

samuel.seide’s picture

My expectation is that the patch should have some checking in it so that if the $item['_remove'] is not available then it won't run that code. Basically the patch should be able to function without it throwing up notices when a default paragraph is being used.

MRPRAVIN’s picture

Adding the patch for removing the single item in the multiple text field values.
It is working fine in 8.3.3 branch

Please review

tacituseu’s picture

tacituseu’s picture

Status: Needs review » Needs work

There's still some JS double-click issue when used within Paragraphs.
Edit: Caused by nested '.field-multiple-table' tables.

tacituseu’s picture

samuel.seide’s picture

@MRPRAVIN Your patch contains a custom module in it - modules/custom/rb_account_profile/rb_account_profile.module b/modules/custom/rb_account_profile/rb_account_profile.module so there's no way to test it...

samuel.seide’s picture

Here is a patch that should work for 8.3.5. See 1038316-131-field-multiple-remove-item-alt.patch if you're running 8.4.x

MRPRAVIN’s picture

@ samuel.seide Thanks for pointing one. I have updated the patch file now. Please test and let us know. May be this patch will be helpful :)

MRPRAVIN’s picture

Version: 8.4.x-dev » 8.3.x-dev
Status: Needs work » Needs review

Here i have attached the patch after @samuel.seide comments reviewed. Please test this patch. I think it will help to someone in Drupal World :) It's works fine in 8.3 Drupal version. Need suggestions also please review.

tacituseu’s picture

MRPRAVIN’s picture

Status: Needs work » Needs review

@here Can someone point out me please why patch gets failed? Based on suggestions i'll work on it.

bleen’s picture

+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/StringTextfieldWidget.php
@@ -23,29 +26,29 @@ class StringTextfieldWidget extends WidgetBase {
-    return [
+    return array(
         'size' => 60,
         'placeholder' => '',
-      ] + parent::defaultSettings();
+      ) + parent::defaultSettings();

I'm not sure why the patch is failing to apply, however there was a coding standards change a few months ago (longer?) that says Drupal expects the "[" notation for arrays (see https://www.drupal.org/docs/develop/standards/coding-standards#array) so this change (which is where the patch fails) is not correct anyway and should be removed from the patch... the same is true of other array notation regressions throughout the patch.

I'd bet a dollar that if you reroll the patch without those coding style regressions that all would be well with the world

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

MRPRAVIN’s picture

@bleen, Thank you for point out me :) Notation for arrays are changed in coding standards. you are correct.

realityloop’s picture

heddn’s picture

Issue summary: View changes
Issue tags: +Needs issue summary update

There seems to be two competing approaches contending in this issue. #143 is with ajax and #145 is with checkboxes. Can we get an IS update and proposed resolution? The issue description is fine, but I don't see what/how we want to fix this agreed upon yet. That makes it really hard to review and provide feedback on patches, because I don't know which is the preferred approach. I only read back through the last 75 or so comments for the past year, and there wasn't any real sense on what has been decided. Can we decide on that before throwing more patches against this thing?

heddn’s picture

Issue summary: View changes
tacituseu’s picture

@heddn: #85 is the last
comprehensive patch on the AJAX path, but as mentioned in #89 it has some problems.

There's a lot of noise - people learning how to make patches, incomplete solutions, re-rolls for obsolete versions, #143 is just a last of "works for my limited use-case" patches (only textfield - started in #128).

heddn’s picture

So, one issue with #143 is that it only supports text fields. And the approach won't scale very well to non-text fields. Meaning, what is built there won't shoehorn into any other field types. So... back to the drawing board?

heddn’s picture

Status: Needs review » Needs work

I spent a fair amount of time banging away at this today. I figured out one thing here close the end of the day... we won't be able to do this without a some changes to field widget base. The latest non JS patch in #143 was done entirely on the sub widget. That doesn't work if you have ajax refreshing in the field. Which I have on my custom field. I'll have to changes some things up in the widget base. Because that's where the item_count is getting calculated and used in formMultipleElements(). In the $max variable.

No patch, because I don't have any code. Just lots of time spent in xdebug trying to figure out this special edge case.

heddn’s picture

FileSize
5.19 KB

Here's a patch (it doesn't work yet). It doesn't seem to be removing the items. But, its moderate progress as we now are moved up to the base class. So all field types will eventually get this functionality.

heddn’s picture

Here's a new patch that seems to work in all cases for my address field. It hasn't been extensively tested, but at least it seems to be functional. Sorry about the interdiff; I don't think it is very readable.

heddn’s picture

FileSize
9.09 KB

OK, this is a proper interdiff for 152.

heddn’s picture

Here's what it looks like fully loaded with several values/locations:

I've added a generous number of example locations on the page. I can remove any one of them or add another one.

When the page first loads with no values/locations, it looks like the following:

One must click add to add the first value/location.

tacituseu’s picture

Issue summary: View changes

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

heddn’s picture

Status: Needs work » Needs review
Issue tags: -Usability +Needs usability review
FileSize
8.49 KB
2.85 KB

Based on some feedback from @alexpott in slack, I've improved the button language to read, "Add item" or "Add another item" depending on if this is the initial item or not.

Also, flagging for usability review.

alexpott’s picture

Reviewing #152.

  1. +++ b/core/lib/Drupal/Core/Field/WidgetBase.php
    @@ -208,36 +208,64 @@ protected function formMultipleElements(FieldItemListInterface $items, array &$f
    +      $wrapper_id = Html::getUniqueId($id_prefix . '-add-more-wrapper');
    

    The wrapper is being used for more than adding more. It is also being used for removing.

  2. +++ b/core/lib/Drupal/Core/Field/WidgetBase.php
    @@ -208,36 +208,64 @@ protected function formMultipleElements(FieldItemListInterface $items, array &$f
    +      foreach ($elements as $element) {
    +        $elements['#prefix'] = '<div id="' . $form['#wrapper_id'] . '">';
             $elements['#suffix'] = '</div>';
    +        $elements['add_more']['#ajax']['wrapper'] = $form['#wrapper_id'];
    +      }
    

    This looks completely unnecessary.

  3. +++ b/core/lib/Drupal/Core/Field/WidgetBase.php
    @@ -208,36 +208,64 @@ protected function formMultipleElements(FieldItemListInterface $items, array &$f
    +        '#value' => t('Add another item'),
    

    Could do something like '#value' => $delta > 0 ? t('Add another item') : t('Add item'),
    Not adding an item by default makes required fields in the single widget form work really nicely.

  4. +++ b/core/lib/Drupal/Core/Field/WidgetBase.php
    @@ -208,36 +208,64 @@ protected function formMultipleElements(FieldItemListInterface $items, array &$f
    +        '#limit_validation_errors' => [array_merge($parents, [$field_name])],
    

    I'd have this set to [] because clicking add more shouldn't validate the exist elements imo.

alexpott’s picture

Also thinking about this some more I thing the remove thing should be optional - or maybe a whole new WidgetBase so existing code and alters still work as expected.

alexpott’s picture

+++ b/core/lib/Drupal/Core/Field/WidgetBase.php
@@ -208,36 +208,63 @@ protected function formMultipleElements(FieldItemListInterface $items, array &$f
+      $form['#wrapper_id'] = $wrapper_id;
...
+              'wrapper' => $form['#wrapper_id'],

I don't think we need $form['#wrapper_id'].

Berdir’s picture

+++ b/core/lib/Drupal/Core/Field/WidgetBase.php
@@ -208,36 +208,63 @@ protected function formMultipleElements(FieldItemListInterface $items, array &$f
+      $id_prefix = implode('-', array_merge($parents, [$field_name]));
+      $wrapper_id = Html::getUniqueId($id_prefix . '-add-wrapper');

I'm wondering in general why it uses a *unique* ID. ajax IDs actually shouldn't be unique because the ajax systems relies on theming being not. For example, it automatically prepends messages to the replaced element. That only works if the ID does not change after the initial replacement happened as otherwise the second ajax command won't be able to prepend before the old ID.

I changed the paragraphs widget recently to a non-unique ID in #2939718: Remove validation of closed paragraphs, fix validation and display of validation message for collapse and it worked fine. Probably the only exception to that would be if you'd have multiple entity forms on the same page but I don't think that is supported, at least not for the same entity type.

I guess it doesn't matter that much for core as both add and remove ignore validation errors and are not expected to have any messages.

alexpott’s picture

+++ b/core/lib/Drupal/Core/Field/WidgetBase.php
@@ -208,36 +208,63 @@ protected function formMultipleElements(FieldItemListInterface $items, array &$f
+      for ($delta = 0; $delta <= $max; $delta++) {
+        $elements[$delta]['actions'] = [
+          '#type' => 'actions',
+          'remove_button' => [
+            '#delta' => $delta,
+            '#name' => implode('_', array_merge($elements[$delta]['#field_parents'], [$field_name, $delta])) . '_remove_button',
+            '#type' => 'submit',
+            '#value' => t('Remove'),
+            '#validate' => [],
+            '#submit' => [[$this, 'ajaxSubmitRemove']],
+            '#limit_validation_errors' => [],
+            '#attributes' => [
+              'class' => ['remove-field-delta--' . $delta],
+            ],
+            '#ajax' => [
+              'callback' => [$this, 'removeAjaxContentRefresh'],
+              'effect' => 'fade',
+              'wrapper' => $form['#wrapper_id'],
+            ],
           ],

If we move the wrapper ID creation to before the loop where we create each single item form we could put this in there and do less looping.

tacituseu’s picture

And from that I guess... consensus has been reached, before another patch was thrown ? ;)

heddn’s picture

Status: Needs work » Needs review
FileSize
8.1 KB
4.28 KB

re #165: not necessarily, but the use of form_state and #ajax does seem to be functional. And its a design paradigm that seems to resonate with a lot of Drupal users.

Ignore the patch in #158. It was overly ambitious. Here's a re-worked on top of #152 and addresses most of the feedback since then. The only thing I haven't touched is how to make it toggleable as suggested in #161.

idebr’s picture

Re #163

I'm wondering in general why it uses a *unique* ID. ajax IDs actually shouldn't be unique because the ajax systems relies on theming being not. For example, it automatically prepends messages to the replaced element. That only works if the ID does not change after the initial replacement happened as otherwise the second ajax command won't be able to prepend before the old ID.

The id attribute must be unique in a document according to the html specifications:

When specified on HTML elements, the id attribute value must be unique amongst all the IDs in the element’s tree and must contain at least one character. The value must not contain any space characters.

https://www.w3.org/TR/html5/dom.html#element-attrdef-global-id

Berdir’s picture

That's not the point. I'm refering to the unique ID API which adds a random suffix.

Of course it has be unique client-side, but when the ajax update happens then we replace that ID with the new content, which then can have the same ID again, there is nothing wrong with that.

If you look at other #ajax, then they almost always even use custom #prefix/suffix with hardcoded markup, exactly to prevent that Drupal messes with those ID's in any way.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

jan.stoeckler’s picture

Status: Needs work » Needs review
FileSize
7.73 KB

Reroll & small tweaks; AJAX-callback should work now. Can't figure out how to generate an interdiff as patch file from #166 doesn't seem to apply.

jan.stoeckler’s picture

Status: Needs work » Needs review
FileSize
8.1 KB
1.33 KB

Addresses/(hopefully) fixes some of the test failures.

cosmicdreams’s picture

  1. +++ b/core/lib/Drupal/Core/Field/WidgetBase.php
    @@ -215,45 +217,64 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface
    +              '#submit' => [[get_class($this), 'submitRemove']],
    ...
    +                'callback' => [get_class($this), 'removeAjaxContentRefresh'],
    ...
    +        '#submit' => [[get_class($this), 'addMoreSubmit']],
    ...
    +          'callback' => [get_class($this), 'addMoreAjax'],
    

    Is get_class($this) necessary? Or will $this suffice?

  2. +++ b/core/lib/Drupal/Core/Field/WidgetBase.php
    @@ -320,6 +341,67 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface
    +    return NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -4));
    

    This seems like it might be cause of the test failures. Why -4?

jan.stoeckler’s picture

1. Is get_class($this) necessary? Or will $this suffice?

I'm not sure, both seem to work, as well as static::class - don't have a preference & will implement whatever's most appropriate.

2. This seems like it might be cause of the test failures. Why -4?

As far as I can tell, we use -4 here to get "up" in the nested array from the triggered submit element to its field element to replace its previous state with it (the whole field element).

jan.stoeckler’s picture

Something that does not yet seem to work correctly: removing items after having changed their order (without saving in between) - this probably needs to be addressed.

tstoeckler’s picture

1. Is get_class($this) necessary? Or will $this suffice?

I'm not sure, both seem to work, as well as static::class - don't have a preference & will implement whatever's most appropriate.

Right, I would go with static::class, as well. As far as I know that always returns the same as get_class($this) (although PHP being PHP after all, I wouldn't be surprised to learn that there are exceptions that that... ;-)).

Regarding the suggestion to just use $this, i.e. to specify [$this, 'submitRemove'] as the callback: While that is a valid PHP callable and - in general - is actually the preferred way because it doesn't require the method in question to be static, it should not be used in the Form API / Render array context, because it is not serializable and Form/Render array may be serialized. That's why I think [static::class, 'submitRemove'] is the way to go.

heddn’s picture

Changed get_class($this) vs static::class

Peter Törnstrand’s picture

I have tested this patch with a content type, Address, with 3 different address fields:

Visiting address
Postal address
Delivery address

All fields can have multiple (unlimited) values.

I edit an existing Address with 1 value for each field.
I first delete the delivery address value.
I then delete the postal address value.

The postal address is deleted but is replaced with the data from the previously deleted delivery address.
The field label of field Postal address is also changed to Delivery address.

Drupal 8.5.6
Address 8.x-1.4
Patch from #179

See https://www.drupal.org/project/address/issues/2992810 for screenshots.

cchoe1’s picture

FileSize
8.95 KB

Hi Peter. I think I'm experiencing similar issues to yours. Is your address field inserted within some sort of wrapper for the form display? I.e. a paragraph reference, a container/fieldset/details/form group, etc, or anything like that? A site I tested this on is using the Link field within a nested paragraph and all the fields we wanted the 'Remove' button on was nested using a form group. I'm not sure what kinds of nesting might affect this but the Form Display for this specific paragraph type wraps all of our components within a 'field_content'. Thus the form displays within a container class on the front-end.

I think this wrapper was breaking the patch at Line 371. As such, I've made a slight correction to it which got it to work on my machine. It was looking for a value $user_input[$field_name] but it seems that it's not finding the value because it's not prefixing the form array with the correct keys. YMMV as I'm not entirely too sure what the actual underlying issue is.

Patch attached below.

cchoe1’s picture

FileSize
8.73 KB

Sorry, some debugging statements got left behind. Here is a cleaner patch:

progzy’s picture

I've tested with patch #183 and got issue.
I've used Paragraphs module (latest stable version just like Drupal , 8.6.5, installed with composer)

I've created a custom content type.
I've created 1 paragraph with a single plain text field and a unlimited link field.
I've added this paragraph as a unlimited field to this content type.

Then created content from previous content type and add 2 instances of the paragraphs with 3 links for each. Saved.

Now on edition if I try to remove second link of first paragraph it is actually removed. Then I try to remove second link of second paragraph, nothing happens (except spinner) when I click "Remove" (Even if I can see an AJAX call in debugger). I click "remove" again and everything is messed up.

Some other combinations also lead to a messy result as well.

cchoe1’s picture

@progzy Thanks for the feedback.

I tried to recreate the same situation as yours. I added the text fields (limit 1) and links (unlimited) to the paragraph and then added the paragraph to a content type with unlimited cardinality. But when I hit 'Remove' on the 2nd paragraph instance, 2nd link, it successfully deleted it.

I'm going to try and recreate your bug on a fresh install but I thought I'd get back to you asap first. If you could, and if you think it's relevant, could you paste the 'Response' from the AJAX call that you see in the debugger?

Running D8.6.5, will try on 8.6.7 but I don't think there should be much of a difference. (Edit: oh I thought I saw you were on 8.6.7 but you're also on 8.6.5. Perhaps one of my custom modules is interfering so regardless, I'll try this patch on a fresh install and see what happens).

progzy’s picture

@cchoe1 you're welcome.

So I did it again. I did it at https://d0cq3.ply.st (admin/admin as user/pwd) so you can try by yourself (It only lasts 12h though). Any way here are the AJAX responses :

- First remove attempt that goes well: https://pastebin.com/raw/WyUbVTZX
- Second remove attempt that fails: https://pastebin.com/raw/THFdp2xS

Hope this helps.

Conf:
- Drupal 8.6.5 (not sure it your patch works with 8.6.7)
- Paragraphs 1.5
- Patch https://www.drupal.org/files/issues/2018-12-17/1038316-183.patch

cchoe1’s picture

Awesome, thanks for setting that up @progzy, I did confirm it was still messing up with your test environment. Looks like when I try to delete l1.2, it replaces that reference with the second paragraph. But then if I try to delete something from l2.*, it brings back l1.*. I'll look into my patch and see if I can't figure out the problem.

cchoe1’s picture

I've attached a patch below, I would not consider my changes to be good code but it makes a proof of concept and seems to work (solves the case above). I've attached a 'TODO: refactor' where we would want to come up with a more intuitive way of assigning a unique ID to the #name attribute.

Drupal Form API requires a '#name' attr on the form element to find the correct triggeringElement. Originally, there would be naming collisions when you have both:
- elements that have the same parent
- elements that are using the same field

When the form is being built, it attaches a delta to the name in an attempt to resolve this. This does not work in all cases--in this case, we are using 2 instances of the same paragraph type. It duplicates the names because deltas will be unique within the paragraph instance but not on the form itself, so two instances of identical paragraph types will result in fields with the same #name such as 'field_link_1_remove_button'.

Now, there should be no duplicated names on the form. Look for the '@TODO' tag (line 225). I'm not sure if this is the best way to name the Remove buttons or not.

To create the unique name, I simply implode the array_parents--no matter what element/field/grouping, this series of items in array_parents should be unique for any form element because it also keeps track of deltas across the entire render array--and then we also attach the local delta (it's position amongst its sibling elements) since that won't be present on array_parents.

Some reading I did:

https://drupal.stackexchange.com/questions/154387/why-does-triggering-el...

https://www.drupal.org/project/drupal/issues/2546700

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

phjou’s picture

I just tested the patch and when I am using the remove button, it removes all the entities of the field instead of removing a specific element.
I'm using this patch with Inline Entity Form as described in this issue: UX issues with the Simple widget

rfbrandsma’s picture

I can confirm the issue phjou addresses.

I would also like to report another issue with the patches so far:
The wrapper id for the add more button no longer is unique. In Drupal core the Html::getUniqueId($id_prefix . '-add-more-wrapper'); is used to create a wrapper_id. The patches are using $wrapper_id = Html::cleanCssIdentifier($id_prefix . '-add-wrapper');.

rfbrandsma’s picture

I can confirm the issue phjou addresses.

I would also like to report another issue with the patches so far:
The wrapper id for the add more button no longer is unique. In Drupal core the Html::getUniqueId($id_prefix . '-add-more-wrapper'); is used to create a wrapper_id. The patches are using $wrapper_id = Html::cleanCssIdentifier($id_prefix . '-add-wrapper');.

prudloff’s picture

I tried the patch in #188. When adding a value to a multivalued field, the AJAX request fails with this error :
Notice : Undefined index: #field_parents dans Drupal\Core\Field\WidgetBase->formMultipleElements() (ligne 227 de /var/www/html/web/core/lib/Drupal/Core/Field/WidgetBase.php)

SpadXIII’s picture

FileSize
9.13 KB

I added the getUniqueId-suggestion by rfbrandsma to the patch in #188. Other than this addition, the patch is identical to the #188 one.

While testing, I didn't see the notice prudloff mentions. I just saw the notice mentioned by prudloff. This was with a field that had no parents.

SpadXIII’s picture

FileSize
9.13 KB

I noticed a little typo in the patch, so here's an updated version.

Tess Bakker’s picture

The Action type element, introduced in #128 conflicts with the Javascript implementation in Drupal.behaviors.dialog.prepareDialogButtons.

Buttons of multiple field values are now displayed in the button area of a dialog.

Screenshot:
Display delete buttons in the button area of a dialog

bartzaalberg’s picture

I've attached a new patch which seems to fix the previously noted issue. Hope this helps

bartzaalberg’s picture

Status: Needs work » Needs review

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

sanfair’s picture

Fix some tests and notices

sanfair’s picture

Fixed for nested fields

semjuel’s picture

I wonder, why don't use Multiple fields remove button module - https://www.drupal.org/project/multiple_fields_remove_button ?
The module version for Drupal 8 works perfectly for me.

dww’s picture

@semjuel: Because this is a task for Drupal core itself. We want the core system to be able to support this functionality, without relying on a contrib module.

Cheers,
-Derek

MrPaulDriver’s picture

@semjue and @dww, yes this does need to be in core.

From screenshots in this issue it appears that efforts are being directed at a text based remove button.

The text button approach was originally used by Multiple Fields Remove Button module, but this has more recently been replaced with a simple delete icon. This works a lot better.

The same approach is also consistent with core ui as used in the media library module.

Please see screenshots

MrPaulDriver’s picture

alexpott’s picture

Priority: Normal » Major
Issue tags: +Usability

I'm upgrading this to a major task based on the usability and feel of adding an text field with unlimited cardinality to core. The result is very confusing because it is not at all apparent how to remove a value once you've added one. I'd go so far to say that the current behaviour is very surprising and not at all accessible.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Denisev’s picture

Tried the patch from #204 for list items inside paragraphs. Having list item 1, 2, 3 and changing their order with dragging to 3, 2, 1 and then deleting the middle number 2, the order reverses after save back to 1, 3 instead of being the expected 3, 1.

Denisev’s picture

This is a modified version of the patch in #204. The changes address the issue with loosing the order of items after save, when having removed items and also reordered them before. There are no new lines added, only a couple removed. Not sure, if this is the correct approach and if nothing else will break. So far it works in our environment.

electrokate’s picture

Neither #204 or #212 is working for me in Drupal 8.8.6. (#212 would not apply, so I made the changes manually to try it). Upon saving, it takes the last element in the $input array and assigns it a negative weight, such that it appears first in the list.

I am trying to fix this but could use some assistance! I can't seem to edit elements of the $input array in the submitRemove function. Perhaps the change needs to be made in the removeAjaxContentRefresh function?

EDITED TO ADD:

I have edited the code and it works for me now. I replaced the submit function as follows:
(Basically the "--key" needs to be inside the if statement that checks for the removed element, and the indices need to be reset afterward.)

    if ($exists) {
      $field_values = [];
      foreach ($field_input as $key => $input) {
        if (is_numeric($key) && $key >= $delta) {
          if ((int) $key === $delta) {
            --$key;
            continue;
          }
        }
        $field_values[$key] = $input;
      }
      NestedArray::setValue($user_input, $parent_element['#parents'], $field_values);
      $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']--;
    }

    $user_input = $form_state->getUserInput();
    $input = NestedArray::getValue($user_input, $parent_element['#parents'], $exists);
    $weight = -1 * $field_state['items_count'];
    foreach ($input as $key => $item) {
      if ($item) {
        $input[$key]['_weight'] = $weight++;
      }
    }
    // Reset indices.
    $input = array_values($input);

    $user_input = $form_state->getUserInput();
    NestedArray::setValue($user_input, $parent_element['#parents'], $input);
    $form_state->setUserInput($user_input);
    $user_input = $form_state->getUserInput();
    static::setWidgetState($parents, $field_name, $form_state, $field_state);
    $form_state->setRebuild();
}

Will follow up with patch.

electrokate’s picture

DuneBL’s picture

Status: Needs work » Needs review
maximpodorov’s picture

Wow. Both issues are closed now. Is this what you planned?
Oh. This one is not closed.

bobbygryzynger’s picture

It looks like the behavior described in #3165908: Item removal AJAX causes infinite nesting is also occurring here since much of the behavior from the contrib module was ported over.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

quietone credited KarenS.

quietone’s picture

anmolgoyal74’s picture

Status: Needs work » Needs review
FileSize
9.94 KB

Re-rolled #214 for 9.2.x. And also updated one of the failing tests.
Not able to generate the interdiff.
Here is the diff for the test.

diff --git a/core/tests/Drupal/FunctionalTests/Entity/ContentEntityFormFieldValidationFilteringTest.php b/core/tests/Drupal/FunctionalTests/Entity/ContentEntityFormFieldValidationFilteringTest.php
index 7bbcd1b27f..f5e7faa450 100644
--- a/core/tests/Drupal/FunctionalTests/Entity/ContentEntityFormFieldValidationFilteringTest.php
+++ b/core/tests/Drupal/FunctionalTests/Entity/ContentEntityFormFieldValidationFilteringTest.php
@@ -135,9 +135,8 @@ public function testFieldWidgetsWithLimitedValidationErrors() {
     // The 'Test multiple' field is the only multi-valued field in the form, so
     // try to add a new item for it. This tests the '#limit_validation_errors'
     // property set by \Drupal\Core\Field\WidgetBase::formMultipleElements().
+    $this->submitForm([], 'Add item');
     $assert_session->elementsCount('css', 'div#edit-test-multiple-wrapper div.form-type-textfield input', 1);
-    $this->submitForm([], 'Add another item');
-    $assert_session->elementsCount('css', 'div#edit-test-multiple-wrapper div.form-type-textfield input', 2);
 
     // Now try to upload a file. This tests the '#limit_validation_errors'
     // property set by
@@ -150,20 +149,19 @@ public function testFieldWidgetsWithLimitedValidationErrors() {
     $this->submitForm($edit, 'Upload');
     $assert_session->elementExists('css', 'input#edit-test-file-0-remove-button');
 
-    // Make the 'Test multiple' field required and check that adding another
-    // item throws a validation error.
+    // Make the 'Test multiple' field required and check saving the form
+    // without entering any value for the required field still throws the
+    // proper validation errors.
     $field_config = FieldConfig::loadByName($this->entityTypeId, $this->entityTypeId, $this->fieldNameMultiple);
     $field_config->setRequired(TRUE);
     $field_config->save();
 
-    $this->drupalPostForm($this->entityTypeId . '/add', [], 'Add another item');
-    $assert_session->pageTextContains('Test multiple (value 1) field is required.');
+    $this->drupalGet($this->entityTypeId . '/add');
 
     // Check that saving the form without entering any value for the required
     // field still throws the proper validation errors.
     $this->submitForm([], 'Save');
-    $assert_session->pageTextContains('Test single field is required.');
-    $assert_session->pageTextContains('Test multiple (value 1) field is required.');
+    $assert_session->pageTextContains('This value should not be null.');
   }
 
 }
fabianfiorotto’s picture

#214 is working for me on 8.9.x but only for unlimited fields. I added a condition to hide the button when the amount of values is limited.

mohit_aghera’s picture

Status: Needs work » Needs review

Changing to needs review so test bot can trigger pipelines.

mohit_aghera’s picture

Status: Needs review » Needs work
ankithashetty’s picture

Status: Needs work » Needs review
FileSize
8.54 KB
789 bytes

Just fixed the custom command failure errors in #225, thanks!

Status: Needs review » Needs work

The last submitted patch, 228: 1038316-228.patch, failed testing. View results

scott.whittaker’s picture

FileSize
8.45 KB

The patch in #228 works for me, except that in my case I have a field with unlimited cardinality and when I edit an existing piece of content which has n items, it only shows n-1 items.

In WidgetBase.php I needed to change the $max from $field_state['items_count'] - 1 to $field_state['items_count'] in order to fix this bug:

    // Determine the number of widgets to display.
    switch ($cardinality) {
      case FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED:
        //$max = $field_state['items_count'] - 1;
        $max = $field_state['items_count'];
        $is_multiple = TRUE;
        break;
scott.whittaker’s picture

scott.whittaker’s picture

Patch based on #228 to fix the unlimited cardinality bug.

Lal_’s picture

Status: Needs work » Needs review

Triggering the test bots

Lal_’s picture

Rebasing #232 to the 9.1.5 version

anmolgoyal74’s picture

Status: Needs review » Needs work

The last submitted patch, 235: 1038316-235.patch, failed testing. View results

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

trishen’s picture

Drupal 9.1.5
patch #235

Bug: pre-filled fields after deletion
Steps to reproduce:
1. Add an unlimited link field to node type
2. Create a node with multiple links
3. Save node
4. Edit node
5. Remove one item
6. Add another item
7. New item is already filled

Same behavior with module multiple_fields_remove_button

alphawebgroup’s picture

Why do we need that in core? Why not in cotrib module like we had in D7 ?

rp7’s picture

@alphawebgroup I believe #209 sums that up pretty well.

linhnm’s picture

#235 is working for me on 9.2.6 but only for unlimited fields. I added a condition to hide the button when the amount of values is limited.

neclimdul’s picture

Love finding an issue so old there are people subscribing with comments...

I've cleaned some things, reverted some changes that seemed unrelated, and applied some earlier fixes that had been lost. Some of those lost fixes like clearing out the form array so config doesn't try to save the remove button should clear up some test failures.

I haven't really finished reviewing the submit handlers. The parent slicing makes me a little nervous because it seems kinda fiddly/brittle since its tied to the structure of the form. Skimming the thread that seems to be confusing other people as well. Submitting now because I wanted to see how this affected the rest of the test suite.

Also, clearly needs tests but that's tagged. Noting because its not really ready for review, just running testsuite.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

maursilveira’s picture

Hello,

I tested the patch #242 and it works as expected.

Thank you!

DrDam’s picture

Hi,

Test against 9.4.x and 9.5.x=> this work fine

Thanks

ordermind’s picture

For me, if I click on the remove button when there is only one field value with data that has been saved to the database already, such as when editing an existing entity, the saved values are displayed again rather than empty values which is the expected behavior according to me.

ordermind’s picture

Another bug I found was that if you delete a value as well as the empty "new value" item, and then click on the button to add another value, the value that you deleted will be displayed. According to me, the expected behavior is that it should be empty.

ordermind’s picture

*** comment removed, this issue was due to code outside of this module ***

siva01 made their first commit to this issue’s fork.

Abhijith S’s picture

Applied patch #242 on 9.4.x.The Remove button appears which can be used to remove field items after applying this patch.
Attaching screen recordings for reference.

ameymudras made their first commit to this issue’s fork.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
FileSize
1.42 KB

The Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

brad.bulger’s picture

Coming here from #93447 with a slight variation - this seems like a huge change to handle the problem.

Will this support a programmatic way to remove values from a multiple value field?

What I am seeing, in 9.5, is that if you have a required multiple value field, and you move the first item out of the first position on the form, it still retains its #required status, and still will fail with a required field error if blank, even though the "first" (from the user's perspective) element in the field has a value.

In my case, the user is not removing the value, my code is making the change. I need to be able to set an item's value to blank, whether it used to be the first item or not. The only workaround at the moment is to rebuild and resort all of the values, and even that is troublesome.

It seems like a bug in its own right that this fails, quite apart from adding a "remove" button as a new feature or whatever.

Berdir’s picture

This is solely a UI issue. If you want to remove an item from a field, you can use \Drupal\Core\TypedData\ListInterface::removeItem().

brad.bulger’s picture

Not from any hook functions that can modify a node form, for instance. The items list is not available, only arrays - user input, form state values, the form array.

solideogloria’s picture

This issue is specifically about making the user interface useable. If you have a problem doing something programmatically, you should create a separate issue.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

perry.franken’s picture

When a site is multilingual the "Add another item" string is not translated. To fix this i created a patch that always uses the singular value.

perry.franken’s picture

FileSize
756 bytes

Intern diff of patch 242 & 262

perry.franken’s picture

FileSize
764 bytes
764 bytes

use "Add another item" instead of "Add item"

perry.franken’s picture

FileSize
5.55 KB

This time with patch.

lauriii’s picture

Issue tags: -Needs usability review

We added the remove button inline with the input in #2521800: List key|label entry field is textarea, which doesn't give guidance towards the expected input. Could we do the same here?

vasike made their first commit to this issue’s fork.

vasike’s picture

Status: Needs work » Needs review

created a new MR for 11.x ... for anyone to join and "close" this ancient issue
- Started from the latest previous patch - #265
- Small refactor there
- Change FormJSAddMoreTest.php to MultipleValueWidgetTest.php to test more things on multiple value widgets ... than just Add more button.
And test remove buttons there.

On #266

We added the remove button inline with the input in #2521800: List key|label entry field is textarea, which doesn't give guidance towards the expected input. Could we do the same here?

I think it's already quite the same.

smustgrave’s picture

See tests have been added so think we can remove that tag

Leaving issue summary if there are anything that needs to be added

Testing by adding a text field
Set to unlimited
Added 3 items to the field
Verified they all get the Remove button
Removed the middle item
Everything saved correctly

But visually could use some tweaks.

ux

Maybe could be done in a follow up but think it looks like a regression too much.

smustgrave’s picture

Status: Needs review » Needs work

oops actually get @lauriii requested the same in #266

vasike’s picture

Status: Needs work » Needs review

about UI
i think we have the follow up in the related issues ...
https://www.drupal.org/project/drupal/issues/3053449
where it seems there is plan for it ...

or there still something to be done here?

lauriii’s picture

Issue summary: View changes
Issue tags: -Needs issue summary update

I think all we need to do is move the remove button to its own column. We can handle rest of the styling in the follow-up. I pushed a commit which does this.

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Works like a charm.

lauriii’s picture

The test failure is due to broken HEAD.

  • lauriii committed be46dafb on 11.x
    Issue #1038316 by tacituseu, vasike, heddn, Eyal Shalev, cchoe1, lauriii...

lauriii’s picture

Status: Reviewed & tested by the community » Fixed

Committed be46daf and pushed to 11.x. Thanks!

neclimdul’s picture

Exciting! This is something my users have been asking for for a long time! Is this something we'll see in D10?

mstrelan’s picture

@neclimdul this will be in 10.2, see https://www.drupal.org/about/core/blog/new-drupal-core-branching-scheme-... for explanation of the 11.x branch

neclimdul’s picture

right right.. that's still confusing.

Fixing my org credits.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

mstrelan’s picture

mahde’s picture

This fix is causing an issue with nested paragraph as it show the "Order" <td> when there is no paragraph value is added.
See the screenshot below:

paragraph

When I removed the updated code in theme.inc file then it works fine.

nicole.harnish’s picture

I am having the same issue as mahde.

Running Drupal 10.2.2

Debugging theme.inc it looks like there are 3 headers to 4 cells, causing an extra

which cascades to the CSS and JS for drag & drop functionality.
lexsoft’s picture

Could someone please let me know how to disable this feature? From what I see, it does not have any UI control, and it's unreasonable to deploy something like this enabled.

Wouldn't the best practice be to deploy it disabled initially, with a checkbox on the field config to enable it? If we have multiple fields with, let's say, 5 entries already displayed, with data present but not visible to the user, giving them the ability to remove it could lead to serious issues.

solideogloria’s picture

Form elements can already be disabled, using the disabled or readonly attributes. We don't need to add a config option for this.

solideogloria’s picture

Also, this issue is closed. If someone has a bug, it should probably be opened as a new issue in the queue.

bkosborne’s picture

Surprised a change record wasn't added for this. Not that it's super disruptive, but admin theme maintainers may have wanted to know about this so they could see how the remove button affected styling of widgets. It also would have allowed me to discover this was a new feature in Drupal 10.2.

robpowell’s picture

Just echoing some of the concerns raised here, having a CR would have been helpful to know about this change quicker. Having this off by default and configurable in the UI would be beneficial incase there are clashes with custom code or contrib like multiple_fields_remove_button.