For some time now I've wanted to write a blog entry about using AHAH to create dynamically generated form elements. After a recent conversation at work regarding usability, I now had a real world example to create: how to use tiered taxonomy to dynamically generate a form. This code snippet will show you how to create a form that creates child select dropdowns based on the parent taxonomy term the user selects.
First I established a multi-tier taxonomy called "AHAH":

For this example I created a menu callback to display my initial form:
<?php
function helper_menu() {
$items = array();
$items['ahah-form'] = array(
'title' => 'AHAH Form',
'page callback' => 'drupal_get_form',
'page arguments' => array('_helper_callback_ahah_form'),
'type' => MENU_CALLBACK,
'access callback' => 'user_access',
'access arguments' => array('access content'),
);
return $items;
}
?>I then defined the page callback to show the initial form:
<?php
function _helper_callback_ahah_form() {
// define an array to contain form elements
$form = array();
// define the top level vid
$vid = 2;
// fetch a tree of taxonomy elements
$tree = taxonomy_get_tree($vid, 0, -1, 1);
// loop though taxonomy and collect elements
$options = array();
foreach ($tree as $key => $value) {
$options[$value->tid] = $value->name;
}
// create the first select dropdown input
$form['select_1'] = array(
'#type' => 'select',
'#options' => $options,
'#title' => t('Select 1'),
'#size' => 5,
'#multiple' => false,
'#ahah' => array(
'event' => 'change',
'path' => 'ahah-form-callback',
'wrapper' => 'wrapper-1',
'method' => 'replace',
),
);
// pass the top level vid in the form
$form['ahah_vid'] = array(
'#type' => 'hidden',
'#value' => $vid,
);
// create an empty form element to contain the second taxonomy dropdown
$form['wrapper_1'] = array(
'#prefix' => '<div id="wrapper-1">',
'#suffix' => '</div>',
'#value' => ' ',
);
// add a form submit button
$form['submit'] = array(
'#value' => 'Submit',
'#type' => 'submit'
);
return $form;
}
?>The above form callback produces the following:

Next, I defined a callback to handle the AHAH page request:
<?php
// new menu item:
function helper_menu() {
// ...
$items['ahah-form-callback'] = array(
'title' => 'AHAH Form Callback',
'page callback' => '_helper_callback_ahah_form_callback',
'type' => MENU_CALLBACK,
'access callback' => 'user_access',
'access arguments' => array('access content'),
);
// ...
return $items;
}
// and, here's the AHAH callback used to create the new form elements:
function _helper_callback_ahah_form_callback() {
// define a string variable to contain callback output
$output = "";
// pull the top level vid from the $_POST data
$vid = $_POST['ahah_vid'];
// pull the selected dropdown from the $_PODT data
$parentVid = $_POST['select_1'];
// loop through the taxonomy tree and fetch child taxonomies
$options = array();
$tree = taxonomy_get_tree($vid, $parentVid, -1, 1);
foreach ($tree as $key => $value) {
$options[$value->tid] = $value->name;
}
// define the second tier select dropdown element
$form['select_2'] = array(
'#type' => 'select',
'#options' => $options,
'#title' => t('Select 2'),
'#size' => 5,
'#multiple' => false,
);
// rebuild form object and output new form elements
$output .= ahah_render($form, 'select_2');
// render form output as JSON
print drupal_to_js(array('data' => $output, 'status' => true));
// exit to avoid rendering the theme layer
exit();
}
// Lastly, here's a help function pulled from Nick Lewis's blog to alter the form
// see: http://www.nicklewis.org/node/967
// NOTE: based on poll module, see: poll_choice_js() function in poll.module
function ahah_render($fields, $name) {
$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[$name] = $fields;
form_set_cache($form_build_id, $form, $form_state);
$form += array(
'#post' => $_POST,
'#programmed' => FALSE,
);
// Rebuild the form.
$form = form_builder($_POST['form_id'], $form, $form_state);
// Render the new output.
$new_form = $form[$name];
return drupal_render($new_form);
}
?>The above code allows the user to select an option from the top level tier of taxonomy and the AHAH callback will generate the a select dropdown of the child taxonomies as shown below:

On form submission, you'll see that the options the user selected as stored in $form_state['values']['select_1'] and $form_state['values']['select_2']





















Comments
Thanks a lot, this was
Thanks a lot, this was incredibly useful.
One minor thing - for some reason, I couldn't get it to work with #value of my wrapper form set to  . Had to change it to an empty space. Any idea why?
Hi Eric, I made a
Hi Eric,
I made a dropdown(categories) which on selction calls #ahah and another dropdown(subcategoires) element is built. Now what i want is on selection of subcategory another ahah should be fired and artilcles should be loaded... but it is not happeningg.... tried to search alot but didnt get anything around.... Does ahah works for the element built from ahah .....
Thnks For your help in advance.
Shireen
What about Edit?
Thanks for the information.Its working for me.but what if iam editing the same form by default i need to show the selected values.Iam not depending on drupal node.
Thanks a lot for the idea and
Thanks a lot for the idea and explanation. It is difficult to imagine what it means to me. The explanation is very important to me. I'm making my first steps in it yet, but have some results. I've found a lot of books at the pdf search engine http://pdf.rapid4me.com . But they are interesting till the moment you begin to undertand. What I need now is a challenge. Thanks that you give them.
Change event
Hi,
Thanks for the tutorial.
I tried the same thing in my form_alter(). But nothing happens on change of first dropdown. Can u pls guide me for this?
ahah
I would suggest starting here:
'#ahah' => array('event' => 'change',
'path' => 'ahah-form-callback',
'wrapper' => 'wrapper-1',
'method' => 'replace',
),
Have you verified that the defined path is a valid callback? Ensure that you can access it directly (example: http://MYSITE/ahah-form-callback).
Also, using a FireFox plugin like Firebug is an essential tool to see what AJAX requests are occurring, and what javascript events are bound to elements.
Three selects scenario
I have tried your snippet and it worked. Thank you very much.
No I need to do a bit more complex drop down dependency by adding one more select, a change the options of this one by selecting the second. I have added menu and ahah call back function for the second the same way as in your example, but for some reasons the second select doesn't fire on change.
Any clue why might be happening?
More than two cascading select boxes
So any one have luck with three select boxes??? To me it look like in second 'select' form #ahah doesn't respond to any event.
firebug
I think firebug (firefox extension) might help. Can you verify that the jQuery is affecting the new form elements?
Thanks for it! There is also
Thanks for it!
There is also one thing.
If selects are #required, we have to do some append work.
Sorry for my crystal English =)
How about in form_alter and using pop-ups
Before i dive into it, thought i'd ask how easy it would be to adapt this code to a hook_form_alter and pop ups? Actually it might be nice for the select one to be a pop-up and the select 2 to be check boxes.
hook_form_alter
Whether you create a new form or edit an existing one with hook_form_alter, it's possible!