- Issue created by @murz
- π¨π¦Canada alberto56
@Murz thanks for posting this. I was going around in circles, and your post set me in the right direction. In my case as well, the triggering element seems wrong.
I would expect:
$form_state->getTriggeringElement()['#name'];
to be the same as:
$form_state->getUserInput()['_triggering_element_name'];
which it is in most cases, but in some cases the former gives me, for some reason, the first triggering element in the page whether or not that's the one I clicked.
$form_state->getUserInput()['_triggering_element_name'];
always seems right.
There is a lot of custom code in my site, so for now I cannot confirm that I'm not doing something which is causing this (although I cannot find anything).
For me, a smelly workaround is the best way forward for now. Thanks!
- π¨πSwitzerland audacus
I had a similar issue building an AJAX form with multiple items.
Each item has its own remove button. When clicking the remove button e.g. of the first item, the triggering element would still be the remove button of the last item.I tried to track the issue down and found following checks in
\Drupal\Core\Form\FormBuilder::elementTriggeredScriptedSubmission
where it will return whether the given element is triggering the submission:protected function elementTriggeredScriptedSubmission($element, FormStateInterface &$form_state) { $input = $form_state->getUserInput(); if (!empty($input['_triggering_element_name']) && $element['#name'] == $input['_triggering_element_name']) { if (empty($input['_triggering_element_value']) || $input['_triggering_element_value'] == $element['#value']) { // If the `#name` and the `#value` match the element in the user input, it is the triggering element... return TRUE; } } return FALSE; }
If you have multiple elements with the same
<!--break-->#name
and the same#value
, the triggering item will be the last item for which the above checks run through.I had something like the following:
for ($i; $i < $num_items; $i++) { $form['wrapper']['items'][$i]['actions']['remove'] = [ '#type' => 'submit', '#value' => $this->t('Remove item'), '#submit' => ['::removeItem'], '#ajax' => [ 'callback' => '::ajaxCallback', ], ]; }
Like this, every remove button had the same
#name
and same#value
, and therefore the triggering element always was the remove button of the last item.I could solve this issue by adding a unique name or value for each remove button:
for ($i; $i < $num_items; $i++) { $form['wrapper']['items'][$i]['actions']['remove'] = [ '#type' => 'submit', // Item specific name. '#name' => 'remove-item-' . $i, // Item specific value. '#value' => $this->t('Remove item @item', ['@item' => $i]), '#submit' => ['::removeItem'], '#ajax' => [ 'callback' => '::ajaxCallback', ], ]; }
- πΊπΈUnited States fholub13
Ran into a similar issue with AJAX wherein there were two custom forms on a page, provided by separate custom modules. One is in the site header as a custom search block and the second as a form class that serves as the content for a custom route. An AJAX handler defined for the second form
submit
element was binding to thesubmit
element of the first form. Both elements had the default id of#edit-submit
.This issue only occurs for anonymous users. The AJAX handler actually binds correctly given the following is true:
- Cache was cleared.
- The first page visited after the cache clear was the custom form route.
After identifying this behavior, the workaround we devised for this particular instance was to modify the submit element of the first form to have custom id and data-drupal-selector attributes while leaving the second form as-is.
Before
$form['container']['submit'] = [ '#type' => 'submit', '#value' => $this->t('Search'), ];
After
$form['container']['submit'] = [ '#type' => 'submit', '#value' => $this->t('Search'), '#attributes' => [ 'id' => 'edit-submit-prod-num', 'data-drupal-selector' => 'edit-submit-prod-num', ], ];