Chili Pepper Design

Web development relleno

Learning Drupal Module Dev: Dependent AHAH/AJAX Forms

| Comments

So, I’ve been working on a simple module for a Drupal project lately. I wanted to have two administrator settings for the module, the second of which depends on the first. A “dependent dropdown”. It would be cool if it was “AJAXy” too, I thought. Well, it just so turns out that in Drupal 6 there are built in AHAH (Asychronous HTML and HTTP) functions in the Forms API to do just this. Easy! Or is it?

It also turns out there is a steep learning curve to making “Ajax forms” with the Drupal Forms API. I got it working, but it took a fair amount of effort. So, to help out future Drupal AHAH developers I am providing my code below, along with a list of links to resources that were a great help in unraveling this problem.

First, to help provide an “aerial view” of what’s going on here, this is a list of the components involved:

  • A form that has the AHAH fields (ahahtestmodule_admin_settings in this example)
  • The first field in the form (ahahtestmodule_types), which will change the contents of the second field
  • The second, “dependent” field (ahahtestmodule_ahah_field)
  • The function that provides the options of the AHAH field based on the first field (ahahtestmodule_get_ahah_fields())
  • The AHAH function that updates the AHAH field (ahahtestmodule_ahah_field_js())
  • menu callback function for the AHAH function (ahahtestmodule/ahahjs)

To start out, here is the example admin settings form ahahtestmodule_admin_settings with both fields (ahahtestmodule_types and ahahtestmodule_ahah_field):

<?php 
function ahahtestmodule_admin_settings() {
  $form = array();
  $form['settings'] = array(
    '#type' => 'fieldset',
    '#title' => t('ahahtestmodule Settings'),
  );
  $form['settings']['ahahtestmodule_types'] = array(
    '#type' => 'radios',
    '#title' => t('First Field'),
    '#description' => t('Change this field to change the options in the next field.'),
    '#options' => array('one' => t('Option 1'), 'two' => t('Option 2'), 'three' => t('Option 3')),
    '#default_value' => variable_get('ahahtestmodule_types', 'one'),
    '#ahah' => array(
      'path' => 'ahahtestmodule/ahahjs',
      'wrapper' => 'ahah-wrapper',
      'method' => 'replace',
    ),
  );
  $form['settings']['ahahtestmodule_ahah_field'] = array(
    '#type' => 'select',
    '#title' => t('Dependent Second Field'),
    '#options' => ahahtestmodule_get_ahah_fields(variable_get('ahahtestmodule_types', 'one')),
    '#default_value' => variable_get('ahahtestmodule_ahah_field', 'none'),
    '#description' => t('This fields content depends on what is selected in the first field.'),
    '#prefix' => '<div id="ahah-wrapper">',
    '#suffix' => '</div>',
  );
  return system_settings_form($form);
}
?>

Next, here is the dummy function that gets the right content for ahahtestmodule_ahah_field based on ahahtestmodule_types:

<?php 
function ahahtestmodule_get_ahah_fields($first_variable) {
  $ahah_fields = array();
  switch ($first_variable) {
    case 'one':
        $ahah_fields['one'] = 'Option 1 Was Selected';
        break;
    case 'two':
        $ahah_fields['two'] = 'Option 2 Was Selected';
        $ahah_fields['two_bonus'] = 'Bonus Option!';
        break;
    case 'three':
        $ahah_fields['three'] = 'Option 3 Was Selected';
        break;
    default:
        $ahah_fields['none'] = 'Please Select...';
  }
  return $ahah_fields;
}
?>

Then, here is the magic AHAH callback function that I don’t fully understand and ripped right off this article at drupal.org: Adding dynamic form elements using AHAH:

<?php 
// The AHAH callback function
function ahahtestmodule_ahah_field_js() {
  // The AHAH callback function triggered by the user changing the first field, "ahahtestmodule_types"
  $form_state = array('storage' => NULL, 'submitted' => FALSE);
  $form_build_id = $_POST['form_build_id'];
  // Get for the form from the cache
  $form = form_get_cache($form_build_id, $form_state);
  // Get the form set up to process
  $args = $form['#parameters'];
  $form_id = array_shift($args);
  $form_state['post'] = $form['#post'] = $_POST;
  $form['#programmed'] = $form['#redirect'] = FALSE;
  // Process the form with drupal_process_form(), which calls the submit handlers that put whatever was worthy of keeping in the $form_state
  drupal_process_form($form_id, $form, $form_state);
  // Call drupal_rebuild_form(), which destroys $_POST, creates the form again with hook_form, gets the new form cached and processed again
  $form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);
  // THIS IS WHAT YOU WILL CUSTOMIZE FOR YOUR OWN FORM
  // Choose the field you want to update with AHAH and render it
  $ahah_form = $form['settings']['ahahtestmodule_ahah_field'];
  unset($ahah_form['#prefix'], $ahah_form['#suffix']);
  $output = drupal_render($ahah_form);
  // Final rendering callback.
  drupal_json(array('status' => TRUE, 'data' => $output));
}
?>

Lastly, be sure to add the menu callback for ahahtestmodule_ahah_field_js():

<?php 
function ahahtestmodule_menu() {
  $items = array();
  $items['ahahtestmodule/ahahjs'] = array(
    'page callback' => 'ahahtestmodule_ahah_field_js',
    'access arguments' => array('administer ahahtestmodule'),
    'type' => MENU_CALLBACK,
  );
  return $items;
}
?>


I rolled this whole thing up into a little demo module that does nothing except run all this code:

Download the Drupal Ahah Test Module

Here are some links that I used to figure this out that will hopefully help you too:

A big thanks to the Drupal community as always for putting so much helpful support up online for free!

Comments