You have to write a menu callback for each AHAH-enabled form item of your form. You have to repeat small variations of this piece of code for each callback:
// Build our new form element. $form_element = _mymodule_add_something_to_form(); // Build the new form. $form_state = array('submitted' => FALSE); $form_build_id = $_POST['form_build_id']; // Add the new element to the stored form. Without adding the element // to the form, Drupal is not aware of this new elements existence and // will not process it. We retreive the cached form, add the element, // and resave. $form = form_get_cache($form_build_id, $form_state); $form['somewhere']['very']['deep'] = $form_element; form_set_cache($form_build_id, $form, $form_state); $form += array( '#post' => $_POST, '#programmed' => FALSE, ); // Rebuild the form. $form = form_builder('mymodule_someform', $form, $form_state); // Render the new output. $subform = $form['somewhere']['choice']; $output = theme('status_messages') . drupal_render($subform); drupal_json(array('status' => TRUE, 'data' => $output));
Ok, it does make sense. But it takes some time to get used to — too much — and is a treshold that’s big enough for many developers to just not implement AHAH forms. It simply shouldn’t be this hard.
This current approach of AHAH forms in Drupal is time consuming, hard to maintain and hard to write tests for.
The other flaws
There also are other flaws:
- Even when a form item has been added for the first time, it’s being validated. This is wrong. You wouldn’t like it if your form was being validated the first time it’s being displayed, right? Well, this is the same thing.
- If new AHAH-enabled form items are added during an AHAH callback, they don’t work, because
Drupal.settingsdoesn’t get updated.
The solution: the AHAH helper module
This module simplifies that. It allows you to:
- not write any menu callback at all.
have a sole, central form definition function that has some if-tests to support a changing form based on the user’s input, i.e. by checking
$form_state['storage']. This is in fact the exact same system you’ve been applying if you’ve already written multi-step forms. This makes sense, because AHAH forms are in fact normal multi-step forms, that just happen to be updatable through AHAH as well.
You still have to use the
#ahahproperty and set a wrapper, but you provide a “magical path” that will automatically rebuild and render the desired part of the form. If the part of the form that you want to be rendered is
$form['fapi']['rocks']then you would do
'path' => ahah_helper_path(array('fapi', 'rocks'))and that’s it.
Adding graceful degradation just became really easy: just create buttons with the appropriate text, set
- skip form validation for form items that exist in the form for the first time (i.e. that are added dynamically). Check if the
#first_timeproperty exists in your validate callbacks.
- have new AHAH-powered form items added in an AHAH callback (previously not supported).
This module is being used right now on Mollom.com, so it’s ready for production. This also obviously is a contribution of Mollom towards the Drupal community, so don’t just thank me (supposing that you’re thankful), but also thank Dries Buytaert and Benjamin Schrauwen.
The module obviously is available at Drupal.org and is considered ready for production use. A demo module is included, which allows you to see what the code looks like. Download it right away!
As always, constructive criticism is welcomed, so see you in the issue queue!
While it works really nicely, I’m sure some aspects are eligible for improvements. I’m aware that a couple of things could be made easier — look at the included
I talked to Nathan ‘quicksketch’ Haug — who wrote the current AHAH forms support — about this, here at DrupalCon Szeged. He immediately agreed that this really really needs to be fixed. So it will make it into core as well, although probably not in the current shape.
I made AHAH forms work in Drupal 5, because I needed it for my Hierarchical Select module. I haven’t had the time yet to do a write-up about this, let me know in the comments if you’d like me to do so.