Follow up from #384992-64: drupal_html_id does not work correctly in AJAX context with multiple forms on page. See the demo module included there. The following code in form_builder() is the problem.

if ($form_state['programmed'] || (!empty($form_state['input']) && (isset($form_state['input']['form_id']) && ($form_state['input']['form_id'] == $form_id)))) {
      $form_state['process_input'] = TRUE;
    }

It needs to match on #build_id either instead of or in addition to $form_id.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

sun’s picture

Status: Active » Needs review
FileSize
1008 bytes

That OP sounds like this patch. Will likely fail though ;)

effulgentsia’s picture

This fixes it and the demo module works if $form_state['cache'] = TRUE; is added to myform(). @rfay: can you please convert your excellent demo module to a test?

sun’s picture

Hm. Why can't we make a uncached form build re-use the form_build_id in $_POST, if existent?

Status: Needs review » Needs work

The last submitted patch, drupal.form-process-build-id.1.patch, failed testing.

effulgentsia’s picture

Why can't we make a uncached form build re-use the form_build_id in $_POST, if existent?

Re-use what form_build_id? The problem is that drupal_get_form($form_id) is being called multiple times during the page request, for different forms, but with the same $form_id (see the demo module). But we want 'process_input' being TRUE only for the form that was submitted. $form_id doesn't identify it uniquely enough. So we need to use #build_id, but that only has meaning if it was cached. If we default forms to reuse the build_id that's in $_POST, then all of the drupal_get_form() calls will end up with the same build_id, and that makes no sense.

effulgentsia’s picture

BTW: I agree that #2 is ugly, both in terms of the new $form_state key it introduces, and because it requires form authors to set $form_state['cache'] when $form_id won't be unique for the page (not at all intuitive). I see this as another problem of using the word 'cache' to mean 'persist': #512026: Move $form_state storage from cache to new state/key-value system, but even a terminology change wouldn't really help here as there are simply a lot of interrelated things that happen as part of form "caching": $form is persisted AND $form_state is persisted AND $form_build_id identifies a form where $form_id isn't sufficient (this issue) or can't be trusted (the reason ajax_process_form() sets $form_state['cache']).

So, alternate ideas for how to fix this would be most welcome!

rfay’s picture

Despite my faulty recollection, this did *not* work in D6 as is. (I backported the bug demonstration module.) Apparently my elaborate dance to work around the form_clean_id() issue also worked around this. I only *encountered* the problem in AHAH context and thought it was strictly a form_id problem.

Working on a test and I'll see if I can look at why the tests failed as well.

I do think that making this work will remove another major WTF. This is a *very* common use case.

sun’s picture

Not wanting to decrease emotions and passion about this issue :), but in my entire lifetime as Drupal developer thus far, I can't recall to ever have wanted the identical $form_id to appear more than once on a page. Yes, my grammar needs work. But this still sounds like an edge-case to me.

rfay’s picture

@sun: This is done in core (I think) on the triggers admin page. It's a no-brainer in E-commerce: You've got an add-to-cart form next to each item for sale on a page. It makes sense to have them all be the same form. That's what's going on with amazon_store.

This also happens in other contexts. For example. you have a page that has a form in it. The same form also happens to appear in a block on that page.

It really should have sane behavior. Yes there are complicated ways to work around it. And probably there's a "best practice" workaround that I don't know about yet.

rfay’s picture

Status: Needs work » Needs review
FileSize
3.35 KB

Imagine my surprise when I wrote the test and it passes with the patch... and without it.

Apparently setting $form_state['cache'] = TRUE makes this a possibility without any changes. Hmm.

This patch is the test only, but has $form_state['cache'] = TRUE.

Edit: It doesn't actually work in my application, though. Too late tonight to figure out why.

effulgentsia’s picture

Status: Needs review » Needs work

I'm not sure why the #10 patch passes. It shouldn't. Something else is going on that's weird.

Anyway, I thought of a way to fix this issue without relying on the form being cached. We could change the hidden input field for 'form_id' to have the value of $form['#id'] instead of $form['#form_id']. Because of #384992: drupal_html_id does not work correctly in AJAX context with multiple forms on page, #id uniquely identifies the form. There's a few details to work out to make that work, but just wanted to share that as an idea: I'm not sure when I'll next have time to pursue it.

But, one problem that immediately comes to mind with that idea is that drupal_process_form() calls drupal_static_reset('drupal_html_id');. This only makes sense if all form processing for the page occurs before all rendering for the page, but that's not always the case, so I'm hoping there's a way to remove that while still addressing whatever that line is meant to accomplish. That line also causes problems if there are multiple instances of the same form, and one of them triggers a validation error.

rfay’s picture

According to the commits in chx's multiform module he made that work with multiple forms with the same form id.

effulgentsia’s picture

Status: Needs work » Needs review
FileSize
8.97 KB

This patch is a variant of what chx has in the multiform module. I reworked and cleaned up the test from #10, ensured it fails in HEAD, and passes with this patch.

effulgentsia’s picture

Same patch, fixed file name to not be misleading.

Status: Needs review » Needs work

The last submitted patch, drupal.multiple_forms-766146-14.patch, failed testing.

effulgentsia’s picture

Status: Needs work » Needs review
FileSize
10.25 KB

Yay, bot!

rfay’s picture

I tested this in a number of contexts, including the original code that gave me grief, and it seems to solve all issues.

+1

rfay’s picture

I extended my example to include #ajax, and it worked. The forms have to be aware enough to make #ajax['wrapper'] unique, but with that, it worked perfectly. I believe that the underlying original issue had to do with D6 AHAH forms and the form_id.

effulgentsia’s picture

Title: form_builder() sets 'process_input' in a way that causes pages with multiple forms with same $form_id to fail » Various bugs on pages with multiple forms with same $form_id
Assigned: Unassigned » effulgentsia
FileSize
12.44 KB

Re #18: adding a #ajax to a form results in $form_state['cache'] being set by ajax_process_form(), which due to a separate bug in drupal_build_form()'s test for when to retrieve from cache makes things appear to work, but it's a misleading illusion. This patch will need more tests added to uncover that illusion and fix the bug.

Since there are several inter-related problems being discovered, I'm trying to merge them into this one issue. So, this patch includes #260934-84: Static caching: when drupal_execute()ing multiple forms with same $form_id in a page request, only the first one is validated and #799356: _form_set_class() is too aggressive in assigning the 'error' class. I will work on additional tests to demonstrate the bugs being fixed.

Leaving as "needs review" to get a bot check, but really, "needs work".

effulgentsia’s picture

Status: Needs review » Needs work

Cool. Bot likes it so far. I need to add more tests though.

effulgentsia’s picture

Issue tags: +D7 Form API challenge

While this issue isn't critical, it touches on deep enough FAPI internals that I think it deserves the "D7 Form API challenge" tag, lest we forget about it.

Damien Tournoud’s picture

Title: Various bugs on pages with multiple forms with same $form_id » Support multiple forms with same $form_id on the same page
Category: bug » feature

Sorry boys, but this really sounds like a feature request. Having several forms with the same form_id on the same page *never* worked, neither in Drupal 5 and below (where it couldn't possibly work because the form cache wasn't introduced), nor in Drupal 6.

I don't see a big need for this, either. In the "add to cart" use case, we already generate a unique form_id per "add to cart" form. Do you really believe it's better to force every of those form on a page (there could be hundreds of them) to store its state in the form cache? :)

rfay’s picture

This worked (except with AHAH) in Drupal 6.

JacobSingh’s picture

Category: feature » bug

Since there is no documentation that you can't do this, and it causes the form API to behave badly if you do it, I think this is a bug. In fact, the side effect is that validation doesn't run on the 2nd form and you may not know it. That's pretty bad IMO.

-J

sun’s picture

Category: bug » task

It's neither really a feature nor really a bug, it just was never considered to be required.

+++ includes/form.inc	17 May 2010 15:35:50 -0000
@@ -75,6 +76,13 @@ function drupal_get_form($form_id) {
   $form_state['build_info']['args'] = $args;
...
+  // It's possible for a page to contain multiple forms using the same $form_id.
+  // These need to be distinguished when the form is submitted.
+  if (!isset($instance_counter[$form_id])) {
+    $instance_counter[$form_id] = 0;
+  }
+  $form_state['build_info']['instance'] = (string) ++$instance_counter[$form_id];

IMO, this belongs into drupal_build_form(). drupal_get_form() is just a convenience helper to ease handling of most forms.

Also. If I'm going to want to use the same form multiple times on the same page, then I will only want to do that, because I need that form with different arguments, right?

In other words:

$form_state['build_info']['instance'] = hash('sha256', $form_state['build_info']['args']);

At least, I fail to see a use-case for outputting the _identical_ form multiple times. (and that should actually work already)

41 critical left. Go review some!

JacobSingh’s picture

yeah, you'd need different arguments. Most common case IMO is if you want a form where you want to edit 5 nodes at once, they would all have the same form ID unless you knew that this broke in some silent deadly way and implemented something in hook_forms...

Also common perhaps is a login form you want to show at the top and bottom of a page, or perhaps a "rate this article" form that goes along with every article? There are really lots of cases for showing the same form multiple times on a page.

I still say it's a bug because the system breaks in a silent nasty way and nothing in the docs say you can't do this + modules could just interact and do it by accident in some cases. But if you insist on changing the type, I'm not going to argue.

rfay’s picture

I think it's a bug too, of course.

sun’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
FileSize
12.67 KB

Basically, this. Code comments explain the situation and idea.

re #26: The login form at top + bottom already works today. All other examples should work with this patch.

Status: Needs review » Needs work

The last submitted patch, drupal.form-id-multiple.28.patch, failed testing.

sun’s picture

+++ includes/form.inc	27 Jul 2010 22:03:26 -0000
@@ -3403,7 +3415,7 @@ function _form_set_class(&$element, $cla
-  if (form_get_error($element)) {
+  if (!empty($element['#validated']) && form_get_error($element)) {
     $element['#attributes']['class'][] = 'error';
   }

Tested manually. This successfully fixes the bug uncovered in #845774-31: Regression: Anonymous users can post comments in the name of registered users

Any clues why Drupal installation fails?

Powered by Dreditor.

andypost’s picture

Strange, installation final step form does not submits. Second attempt submit works.
I mean _install_configure_form() does not submits from first attempt

effulgentsia’s picture

Not sure if we'll manage to fix all of the bugs related to multiple forms with the same $form_id coexisting in the same page in time for D7. I hope we do, but if we don't, #757154: Base form_id via hook_forms() not taken into account for #validate, #submit, hook_form_FORMID_alter() makes the workaround currently employed by D6 contrib modules of using hook_forms() to map unique form ids to a particular base form, much easier.

sun’s picture

Assigned: effulgentsia » sun
Status: Needs work » Needs review
FileSize
12.93 KB

Tested manual installation. It indeed fails for yet unknown reasons when trying to submit the install_configure_form - it gets simply displayed again, without any submitted values.

The logic is triggered by this code, but that's not the cause:

function install_run_task($task, &$install_state) {
  if ($task['type'] == 'form') {
    if ($install_state['interactive']) {
      $form_state = array(
        'build_info' => array('args' => array(&$install_state)),
        'no_redirect' => TRUE,
      );
      $form = drupal_build_form($function, $form_state);
      // If a successful form submission did not occur, the form needs to be
      // rendered, which means the task is not complete yet.
      if (empty($form_state['executed'])) {
        $install_state['task_not_complete'] = TRUE;
        return drupal_render($form);
      }
      // Otherwise, return nothing so the next task will run in the same
      // request.
      return;

So 'executed' is not set, but 'executed' is merely:

function drupal_process_form($form_id, &$form, &$form_state) {
  if ($form_state['process_input']) {
    if ($form_state['submitted'] && !form_get_errors() && !$form_state['rebuild']) {
      $form_state['executed'] = TRUE;

Whereas 'submitted' is merely:

function form_builder($form_id, $element, &$form_state) {
  if (isset($element['#type']) && $element['#type'] == 'form') {
    // If the triggering element executes submit handlers, then set the form
    // state key that's needed for those handlers to run.
    if (!empty($form_state['triggering_element']['#executes_submit_callback'])) {
      $form_state['submitted'] = TRUE;
    }

So it must be 'process_input', which happens to be altered here, and which should be fixed in attached patch. :)

Status: Needs review » Needs work

The last submitted patch, drupal.form-id-multiple.32.patch, failed testing.

sun’s picture

ok, I'm officially baffled now. The patch failing again to install means that only my second attempt to submit the install_configure_form worked out, while the first does not... (?)

sun’s picture

Status: Needs work » Needs review
FileSize
13.13 KB

$install_state is specially prepared during installation.

Status: Needs review » Needs work

The last submitted patch, drupal.form-id-multiple.36.patch, failed testing.

sun’s picture

Status: Needs work » Needs review
FileSize
15.81 KB

Form rebuilds might be an issue though... trying to cope for them.

Status: Needs review » Needs work

The last submitted patch, drupal.form-id-multiple.38.patch, failed testing.

sun’s picture

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

Although badly needed, this is D8 material according to the rules (I had to learn today). It may be backported at a later point in time (though that's unlikely).

Dave Reid’s picture

bjaspan’s picture

I stumbled upon this problem and proposed a different approach in #1057968: Support dynamically generated form ids. The approach here embeds unique information about the form in the 'instance' property, passed as a hidden value through the browser. My approach embeds unique information in the form_id and offers a direct method of specifying the correct form builder function. I have not written a patch, but I suspect that my approach is simpler though it does not protect against accidentally putting the same form (with the same id) on a page twice and then having the wrong form processed and/or the second form submitted without validation.

Does anyone want to argue that one or the other approach is definitely better?

drm’s picture

FYI, I'm running into an issue with Commerce where a View has the same product in it multiple times, and thus multiple versions of the same add-to-cart form. When you update what we always called an attribute, the FAPI ajax causes some of the duplicated add-to-cart forms to disappear. Multiple copies of the same product are showing up because the View is actually displaying a different node type that has references to products, and with this other content type, more than one node can reference the same product.

All we want is for the add-to-cart forms to stop disappearing. I'm not sure if this is the exact same bug, but it seems so.

attiks’s picture

Anonymous’s picture

I ran into this issue today as well. I'm displaying multiple add to cart forms on the same page, and when one gets submitted, it is the first instance of the form that is submitted, not the one I clicked.

If we could get this put into Drupal 7, that would be a real lifesaver :)

-Leighton

rfay’s picture

@wildkatana I accomplished exactly the same thing in Amazon Store using hook_forms(). Quirky, but do-able. I'd rather see this one actually solved though.

Anonymous’s picture

I was just about to post the same thing rfay. The hook_forms() functionality lets you create multiple versions of the same form with different ids, that's what I just did as well. If this patch were committed, we wouldn't need to do it, but at least this bug isn't a showstopper.

-Leighton

andypost’s picture

arnoldbird’s picture

#42 works for me.

jibran’s picture

Issue summary: View changes
Status: Needs work » Active

No D8 patch yet.

Fabianx’s picture

Category: Task » Bug report
Priority: Normal » Major

That is a major bug in my book and not a normal task ...

Wim Leers’s picture

#51: Please clarify why.

effulgentsia’s picture

Assigned: sun » Unassigned
Issue tags: -D7 Form API challenge

>4 years since #33, so unassigning. I don't see any way for this to be backported to 7.x, so removing the "D7 Form API Challenge" tag: we missed that boat.

I agree with this being a bug, especially for D8, because:
- You can place multiple instances of a block on a page, so for any block that presents a form, this can be triggered via that.
- In #2110951: Remove hook_forms(), we removed hook_forms(), so I'm not currently clear on how a contrib module could work around this problem in the way it could have in prior versions.

I think something along the lines of #38 has promise.

effulgentsia’s picture

Tagging "Contributed project blocker", because it would be great to investigate / get feedback on whether there are contrib modules hard blocked on this. For a module that controls its own forms (e.g., Commerce's "add to cart"), the module can implement its own dynamic getFormId() method. But I wonder if there are contrib modules that build some kind of multi-form-instance functionality around forms they don't control, for which they could have used hook_forms() in 7.x, but can't in 8.x.

Berdir’s picture

The way a module can work around it is by implementing something that returns a unique string from the getFormId() method. We no longer need the hook as we don't call the form by the form id anymore, we call it by the class.

See SimplenewsSubscriptionBlock::build() for an example how we solved it with simplenews subscription forms in blocks. (wouldn't be surprised if some of the recent/upcoming) form changes will require some changes there.,..

I don't really see a way around enforcing unique form ID's but maybe all the form submission changes do allow something here, I haven't been following them closely.

There were two challenges in making it work for simplenews:

a) Injecting a value into the form class that we can use *before* getFormId() is called. As you can see, that does require quite a bit of code right now. Further complicated in our case by the fact that it is an entity form.

b) The fact that blocks no longer have a (reliable) unique ID associated to them. We have to generate/maintain a UUID ourself in our configuration to actually have something to identify a certain block form. I've discussed with @timplunkett a long time ago that it would make cases like this a lot easier. That unique ID has to exist anyway (the config entity ID for core blocks, the UUID for page manager blocks), it's just not accessible.

andypost’s picture

andypost’s picture

Also more then one comment form could exist same time on one page so modules like ajax_comments could have issues

Dave Reid’s picture

It would be nice if File Entity didn't have to rely on the hacky Multiform module in order to support editing of multiple file entities after doing a bulk upload.

Berdir’s picture

@Dave Reid: I'm fairly sure that's a different problem, your use case is having multiple, usually separate entity forms as a single form that you can submit together. So there really is only a single form. This is about having the same (configurable/re-usable) form multiple times on the same page, without knowing about the others. So kind of the opposite use case..

tim.plunkett’s picture

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

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

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

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.

Andre-B’s picture

Following up on https://www.drupal.org/node/766146#comment-10018657:

Tagging "Contributed project blocker", because it would be great to investigate / get feedback on whether there are contrib modules hard blocked on this.

We are running into this issue at the moment with https://www.drupal.org/project/votingapi_widgets

Inside of VotingApiWidgetBase we are calling entity form builder for a vote entity.

  public function getForm($entity_type, $entity_bundle, $entity_id, $vote_type, $field_name, $style, $show_results, $read_only) {
    $vote = $this->getEntityForVoting($entity_type, $entity_bundle, $entity_id, $vote_type, $field_name);
    return \Drupal::service('entity.form_builder')->getForm($vote, 'votingapi_' . $this->getPluginId(), [
      'read_only' => $read_only,
      'options' => $this->getPluginDefinition()['values'],
      'style' => $style,
      'show_results' => $show_results,
      'plugin' => $this,
    ]);
  }

These forms will be printed separately on the requested page. Examples are:

  • Views with content and rating forms on them.
  • Node view with comments, node can be rated and comments can be rated.

In the last case core currently takes the first form it renders for all of those ajax requests. I am trying to investigate further and will have a look on the patch if it fixes that issue for us. In our case we could still extend EntityFormBuilder and change the logic of getForm() to add unique form ids. Issue in votingapi_widgets: #2832153: Multiple VotingAPI Widgets on the same page do not work correctly

Berdir’s picture

You need to let it generate a unique form ID. You have the entity there, so override getFormId() and return something unique, including the entity type and ID should be a good start, then you'd only get in trouble if the same entity is displayed more than once, but that's then also kind of the same form anyway.

Andre-B’s picture

@Berdir, thats exactly what I tried and figured out it's not supported. since form_state is not passed onto FormInterface::getFormId in see FormBuilder::getFormId for reference.

To solve this I extended FormBuilder and Injected this custom FormBuilder in an new EntityFormBuilder instance. But this was only possible because I have some control over how I retrieve the form. I think extending FormInterface::getFormId by optional $form_state and passing this over in EntityFormBuilder can help others - or enabling multiple form ids.

tim.plunkett’s picture

Inside your EntityFormInterface::getFormId you have access to the entity object, isn't that enough?

Andre-B’s picture

@tim.plunkett missed that one. thank you! http://cgit.drupalcode.org/votingapi_widgets/commit/?id=6257f65

Berdir’s picture

Right. And if that is not enough or if you have a plain form, you can instantiate the form object yourself and then pass in data, e.g. using setters and pass an object to getForm: http://cgit.drupalcode.org/simplenews/tree/src/Plugin/Block/SimplenewsSu...

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.

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

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should 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.

rp7’s picture

I have a use case where I need to display multiple profile forms (https://www.drupal.org/project/profile) on the same page. The first form was always saved, not the one I was editing. Took me a while to find this issue but here we are.

I was able to fix this issue by overriding the ProfileForm handler. It overrides the getFormId() function in order to return a form id based on the entity the form is being built for. Just putting this out here for people needing to do the same for entity types they don't have control over (eg. core/contrib ones).

/**
 * Implements hook_entity_type_alter().
 */
function mymodule_entity_type_alter(array &$entity_types) {
  // Change the form class for profiles to our custom implementation.
  $entity_types['profile']->setFormClass('default', 'Drupal\mymodule\Form\MyProfileForm');
  $entity_types['profile']->setFormClass('add', 'Drupal\mymodule\Form\MyProfileForm');
  $entity_types['profile']->setFormClass('edit', 'Drupal\mymodule\Form\MyProfileForm');
}
namespace Drupal\mymodule\Form;

use Drupal\profile\Form\ProfileForm;

class MyProfileForm extends ProfileForm {

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    $form_id = $this->entity->getEntityTypeId();
    if ($this->entity->getEntityType()->hasKey('bundle')) {
      $form_id .= '_' . $this->entity->bundle();
    }
    if ($this->operation != 'default') {
      $form_id .= '_' . $this->operation;
    }
    if ($this->entity->id()) {
      $form_id .= '_' . $this->entity->id();
    }

    return $form_id . '_form';
  }

}

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

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should 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.

Shashwat Purav’s picture

Changing the wrapper fixed the issue:

E.g.

If there are two forms and has same wrappers as below:

$form['wrapper'] = [
'#markup' => '

',
];

Change one of the wrappers and IDs:

$form['wrapper-form'] = [
'#markup' => '

',
];

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

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should 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.

markus_petrux’s picture

Generating different form ids is not ideal because it affects template suggestions... so you have to build custom template suggestions to workaround that.

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

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.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.

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

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should 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.

geek-merlin’s picture

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

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

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

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

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should 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.

smustgrave’s picture

is having multiple forms with a same ID not cause an accessibility issue?

andypost’s picture

No, real IDs are different and if it's 2 node forms then development surely added wrappers or other visual notes about which form is for

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

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should 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.

Anybody’s picture

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

Just ran into this issue in Homebox (3.0.x) development and think I can add a good example from there:

All Homebox portlets (boxes) are separate forms, based on the same form (id). So there can be unlimited number of these "same" forms on the same page.

Technically, they are extending PluginBase and implement FormInterface. (The ECA module does similar things and for this software design, this is needed).

Here's the class implementation:

abstract class HomeboxPortletTypeBase extends PluginBase implements HomeboxPortletTypeInterface, ConfigurableInterface, PluginFormInterface, ContainerFactoryPluginInterface, FormInterface

When implementing the AJAX functionality, I was wondering, why

$form_state->getTriggeringElement();

always returned the wrong element! It always returned the first buttom from the very first instance of the form on that page.
The interesting part is, that the AJAX call body contained the clicked element as triggering element.

So I tried changing the getFormId() implementation in that plugin from:

/**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'homebox_portlet_form';
  }

to

/**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'homebox_portlet_form' . $this->getDelta();
  }

like in #71 and it works for now! But it cost me hours to find this.

I guess the reason for this is a security check in the form to ensure the AJAX-given triggering element exists.
Perhaps a first step would be to log a watchdog warning in such a case to let the developer know the mismatch?

Hopefully my implementation for the dynamic form ID won't have other side-effects! For now it works...

So this is still an issue in 11.x-dev!

Re #82 see #1852090: Cached forms can have duplicate HTML IDs, which disrupts accessible form labels

Anybody’s picture

aragon-usr’s picture

Had same problem,
I have multiple same forms on one page generated in loop.
I met the problem when I wanted to inject HttpClient to Form but back then was passing customValue through constructor.

My approach to solve this issue was define Form as service and pass custom id via property setter:

.services.yml

services:
  my_module.my_form:
    class: Drupal\my_module\Form\MyForm
    arguments: [ '@http_client' ]
    tags:
      - { name: form }

CustomClass:

      $form_as_service = \Drupal::service('my_module.custom_form');
      $form_as_service->setCustomValue($custom_value);
      $form = $this->formBuilder->getForm($form_as_service, $additional_parameters);

Form Class:

      protected string $customValue;

      public function setCustomValue(string $value) {
            $this->customValue = $value;
      }

      public function getFormId() {
            return 'base_id_'.$this->customValue;
      }

Maybe it's not ideal, but I think it could be helpful for others.