27
Jun
2013

AJAX Buttons and how to make Drupal less dumb about which was clicked

I've struggled with this in the past, and every time I work on a project with a custom FAPI form that involves multiple elements and submit buttons to either add another, or do some other fun AJAXy goodness it seems to rear its head again. In the past I've found various messy ways to resolve the problem, but I think I've finally discovered the real "clean" way.

What's the problem I'm talking about?

Suppose you have three instances of a custom compound field, each with an ajax button to go perform some action and update another element in the "field" ... Initial testing seems to be working fine, until you actually add multiple instances and realize that no matter which button you click it's behaving as if the LAST instance of that button was clicked.  Furthermore, I can never seem to find the solution documented anywhere.  Infuriating! Surely I'm not the ONLY person who struggles with this.

As it turns out, the solution is actually pretty simple.  It seems the problem, or part of the problem anyway, causing this to happen is that Drupal Form API button elements ('#type' => 'button') by default all have the same '#name' attribute assigned to them, 'op'.  By simply overriding the name attribute and including the delta, we can ensure that each instance of the button in the form, not matter how many there are, all have a unique name, and this seems to be all that's necessary for the rest of the form system to properly latch onto the correct button as the one having been pressed.

To clarify here's a bit of code.

First the problematic code:

$widget_form['something_awesome'] = array(
  '#type'  => 'button',
  '#value' => t('Do Something Awesome!'),
  '#ajax'  => array(
    'callback' => 'MYMODULE_process_something_awesome',
    'wrapper'  => 'my-awesome-wrapper-' . $delta,
    'method'   => 'replace',
    'effect'   => 'fade',
  ),
);

And now, the simple correction to solve the problem:

$widget_form['something_awesome'] = array(
  '#type'  => 'button',
  '#name'  => 'awesome-button-' . #delta,
  '#value' => t('Do Something Awesome!'),
  '#ajax'  => array(
    'callback' => 'MYMODULE_process_something_awesome',
    'wrapper'  => 'my-awesome-wrapper-' . $delta,
    'method'   => 'replace',
    'effect'   => 'fade',
  ),
);

The other way to do this would be to change the value attribute on each instance, but that gets ugly since then you have differently named buttons, and if I remember correctly, you have to include some kind of messy code to fish out which button was actually clicked and act accordingly.

If you find yourself fighting this issue, try setting the #name attribute for each instance including the delta, so that each instance of your button will have a unique name in the rendered markup.