NOTE: "#ahah" in forms was changed to "#ajax" in Drupal 7, so this post has been revised from D6 and D7 to just D6. Look in the Examples module (ajax_example) to find out more about AJAX in forms in D7.

Developers are often confused about AHAH in Drupal. The acronym usually refers to a subset of Ajax functionality. While Ajax usually involves returning an XML document, which needs to be parsed, in AHAH you simply return HTML ready to be loaded into a DOM element of choice.

In Drupal, however, AHAH has a particular meaning. It all started with the AHAH Forms Framework module for Drupal 5, the purpose of which was to allow developers to add new form elements to a form without reloading the page. By adding an '#ahah' property to a form element, say a button, specifying the callback function that would build the new elements, the effect (e.g. fade or slide) you wanted, and the event on which your callback should be called (defaulting to 'click'), your button would deliver the new elements as soon as user clicked the button, without the need to write a single line of JavaScript.

The functionality was later incorporated into Drupal 6, and this is why the term AHAH now refers exclusively to the dynamic rendering of form elements in Drupal. If you are just looking to load some normal content (as opposed to form elements) dynamically, then AHAH, in this Drupal sense, is not the right tool for you - have a look at section 6 of this guide, Ajax in Drupal using jQuery. In fact, the difference that originally led to the coining of the term AHAH - that it didn't involve XML, whereas Ajax did - is no longer considered significant and it's pretty much all referred to as Ajax these days.

For the special case that is AHAH in Drupal, while it is true that this built-in functionality eliminates the need for you to write your own JavaScript to achieve dynamically loaded elements, there are a number of important steps that can be tricky. The most significant is the callback function, which is discussed in the subsection, Adding dynamic form elements using AHAH. The more basic steps are best explained by example, so let's look at how it's done in Poll module. The poll creation form has a "More choices" button which, when clicked, dynamically adds a new set of poll choice fields (one field for the text and one field for the initial number of votes). Here's how the AHAH is set up in the form:

// the following code is from the poll_form() function in poll.module

  // Add a wrapper for the choices and more button.
  $form['choice_wrapper'] = array(
    '#tree' => FALSE,
    '#weight' => -4,
    '#prefix' => '<div class="clear-block" id="poll-choice-wrapper">',
    '#suffix' => '</div>',
  );

  // Container for just the poll choices.
  $form['choice_wrapper']['choice'] = array(
    '#prefix' => '<div id="poll-choices">',
    '#suffix' => '</div>',
    '#theme' => 'poll_choices',
  );

  // Add the current choices to the form.
  for ($delta = 0; $delta < $choice_count; $delta++) {
    $text = isset($node->choice[$delta]['chtext']) ? $node->choice[$delta]['chtext'] : '';
    $votes = isset($node->choice[$delta]['chvotes']) ? $node->choice[$delta]['chvotes'] : 0;

    $form['choice_wrapper']['choice'][$delta] = _poll_choice_form($delta, $text, $votes);
  }

  // We name our button 'poll_more' to avoid conflicts with other modules using
  // AHAH-enabled buttons with the id 'more'.
  $form['choice_wrapper']['poll_more'] = array(
    '#type' => 'submit',
    '#value' => t('More choices'),
    '#description' => t("If the amount of boxes above isn't enough, click here to add more choices."),
    '#weight' => 1,
    '#submit' => array('poll_more_choices_submit'), // If no javascript action.
    '#ahah' => array(
      'path' => 'poll/js',
      'wrapper' => 'poll-choices',
      'method' => 'replace',
      'effect' => 'fade',
    ),
  );

First, it creates a wrapper to hold all of the AHAH elements, including the button itself. Then it creates a special wrapper for the choices themselves; all existing choices are added inside this wrapper. Then this wrapper is referenced in the #ahah property of the button element ($form['choice_wrapper']['poll_more']). In effect, what the #ahah property is saying here is "When a user clicks me (remember the default event is 'click'), fetch new elements from the path 'poll/js', stick them into the div with the id of 'poll-choices', replacing the existing contents of that div (as opposed to appending to them, which is another option), and use the 'fade' effect when making the switch". Another option would be, for example, to use a different effect instead of 'fade' or to have the behavior bound to the change event of a dropdown element instead of the click event of a button. In this case, you would add the 'event' parameter to your '#ahah' property and set its value to 'change'.

The code in misc/ahah.js looks after the jQuery side of things, binding the desired behavior to your element and making the Ajax request to your specified callback. Now you need to create your callback function and submit handler and make sure your form-building function is built correctly. See the subsection Adding dynamic form elements using AHAH for an explanation of how to avoid the many pitfalls you could fall into at this juncture.

Comments

Pasqualle’s picture

according to http://api.drupal.org/api/file/developer/topics/forms_api_reference.html the submit type does not have a #description property.

It probably should be changed to:

'#attributes' => array('title' => t("If the amount of boxes above isn't enough, click here to add more choices.")),
gagarine’s picture

https://interface-network.com - Interface Network is an action and research technology governance agency.

calebgilbert’s picture

I *really* appreciate that this post points out that the context in which Drupal uses the term "AHAH" bears no relation to the context that "AHAH" is normally used in. IMHO, this situation should be changed in and not just footnoted since it is so likely to be the source of confusion for newcomers to Drupal/AHAH.

UPDATE: From Katherine Bailey (acknowledge Drupal/AJAX/AHAH ninja), "thankfully that misnomer will fall by the wayside in D7 - it's all just ajax, including dynamically rendering form elements".

Karlheinz’s picture

I had a hard time with this, and I'd like to share some common pitfalls I came across:

  • Make sure you pass $form_state to hook_form(). A couple of modules in the API (notably node_example.module) do not pass this value in the example code, and if you're starting out by copying that code, you'll need to change this.
  • When adding more form fields/array values in $form, you must set the parent form element's #tree value to TRUE. Otherwise, Drupal's form handler will collapse the dynamic fields into one field (the last one). In the case of the poll module, it's set on the $form['choice_wrapper']['choice'][$delta] field (in the helper function _poll_choice_form()).
  • On the other hand, setting #tree to FALSE anywhere in the form will "collapse" the tree for the entire form. This is relevant if, for example, you have a dynamic block of fields that includes a fieldset. In this case, you would need to set the #parent value on the dynamic fields within the fieldset.
  • Don't forget to register the callback function in hook_menu().
  • Since your module is not in core (hence you don't know for sure where it is in the filesystem), a better way to include node.pages.inc is to use module_load_include() instead:
    module_load_include('inc', 'node', 'node.pages');
  • I have occasionally gotten errors relating to array_shift() when the AHAH callback is executed. I believe (but am not sure) that this happens if you make a mistake in your code, and the array that holds the dynamic field is empty.

-Karlheinz

GuyPaddock’s picture

One important note that is missing from the docs is that what you specify for #prefix and #suffix is actually rendered only if the theme function returns content. In the case of the Poll module, $choice_count is always at least 2 (see the code that immediately precedes the snippet provided here), so there are always at least two fields inside the DIV AHAH replaces.

If, however, you are writing a form that starts off without anything inside a DIV, and AHAH is supposed to populate that DIV later (for example, something like the "Shipping Quotes" feature of Ubercart, which requires user input first), you have to make sure that the theme function you define for that element returns anything other than an empty string for the initial load. Otherwise, the #prefix and #suffix will never render and AHAH won't have anything to populate after the callback from the server returns.

The reason for this is that drupal_render() in Drupal core will not render an empty markup element.

veeraprasadd’s picture

Example here : AHAH Example

Regards,
Veera Prasad Dagudu

naught101’s picture

Some more info here, including detailed examples: https://drupal.org/node/752056

alessio89’s picture

I tried "ajax_example" from examples-7.x-1.x-dev and i got the error.The error appears as a popup when I change a select option.

Firefox: An error occurred while attempting to process /drupal/en/system/ajax: this._each is not a function

Chrome: an error occurred while attempting to process /drupal/en/system/ajax: Object [object DOMWindow] has no method '_each'

I have no clue what it can be...