Support multiple forms with same $form_id on the same page

Created on 8 April 2010, over 14 years ago
Updated 10 May 2024, 8 months ago

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.

🐛 Bug report
Status

Active

Version

11.0 🔥

Component
Form 

Last updated 1 day ago

Created by

🇺🇸United States effulgentsia

Live updates comments and jobs are added and updated live.
  • Contributed project blocker

    It denotes an issue that prevents porting of a contributed project to the stable version of Drupal due to missing APIs, regressions, and so on.

  • Needs issue summary update

    Issue summaries save everyone time if they are kept up-to-date. See Update issue summary task instructions.

Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • 🇩🇪Germany Anybody Porta Westfalica

    Just ran into this issue in Homebox 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();
      }
    

    and it works!
    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!

  • 🇩🇪Germany Anybody Porta Westfalica
  • 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.

  • 🇮🇳India sukr_s

    An alternate solution if multiple forms with different constructor values are rendered on same page #2821852-41: The same form twice on one page with different arguments may process the wrong form when submitted

Production build 0.71.5 2024