Introduction to Ajax-enabled Forms
Ajax-enabled forms in Drupal 7 offer dynamic form behavior with no page reloads. They are an extension of the Drupal Form API.
What is dynamic behavior? Traditional web behavior has the user fill in a form, click a button, and the entire page is rebuilt and sent back to the browser. Ajax-enabled forms update or replace part of the page or part of the form without doing a full page reload - only the part that needs to be changed is changed. It's more responsive to the user and typically faster than the traditional page reload approach.
Some facts about Ajax:
Ajax forms provide dynamic form behavior without page reloads.
They're significantly simplified in Drupal 7.
As a developer you don't use or touch any JavaScript to create an Ajax-enabled form. (see Warnings section below)
Ajax forms are a close relative of multistep forms.
Most of the time, Ajax-enabled forms are a dynamic replacement of an HTML region on the page, which is most often a piece of a rebuilt form.
Some background:
Before Drupal 7, Ajax forms were referred to as AHAH forms because Ajax (Asynchronous JavaScript and XML) implied that XML was involved but the Drupal technique doesn't use any XML (and the JavaScript part is behind the scenes). In Drupal 7 the terminology was changed to use the common and recognizable "Ajax" even though it is not literally exact. Also before Drupal 7, there was quite a lot of black magic required to get the background form submission to work correctly. All of that has been standardized and moved into Drupal core.
There are plenty of examples of Ajax behavior in Drupal that have nothing to do with Ajax forms. For example, the Fivestar module uses its own Ajax implementation to communicate a vote from the browser to the server without a page reload.
The Big Idea
The big idea here is that:
Your form gets rebuilt when you manipulate a form element (e.g. select, submit etc.)
Your form builder function builds it a different way based on that input ($form_state)
Your #ajax settings and your callback function arrange to deliver all or a part of the newly rebuilt form to replace or otherwise enhance some part of the page.
The Basics
To create an Ajax-enabled form, you:
Mark a form element as an Ajax-enabled using the #ajax property. This form element will now trigger a background Ajax call when it is changed or clicked.
The #ajax['wrapper'] property includes the HTML ID of a page section that should be replaced (or altered in some other way).
The #ajax['callback'] tells the Form system what callback should be called after the Ajax call happens and the form is rebuilt.
Create a callback function (named by the #ajax['callback']). This is generally a very simple function which does nothing but select and return the portion of the form that is to be replaced on the original page. Note, that as part of the Form APIs security system, you cannot create new form elements in the callback function, as they will throw errors upon submission, and any #ajax on elements created in the callback will also not work. If you need to create new elements on ajax submit, they must be added in the form definition. You can use the values in $form_state to determine whether the form build process is the initial form load, or an #ajax initiated load.
In the form definition, access the values of one or more elements of the form (usually the triggering element, $form_state['values']['howmany_select'] in the example below) to conditionally affect the form element with the wrapper ID ($form['checkboxes_fieldset'], in the example, and how it is modified is only shown in the In More Detail section below.
In the Examples Module "Ajax Example: generate checkboxes" example, the Ajax-enabled element is a select, $form['howmany_select'], which causes replacement of the HTML ID 'checkboxes-div' (named in #ajax['wrapper']), which is a wrapper around a the fieldset $form['checkboxes_fieldset']:
/**
* Ajax-enabled select element causes replacement of a set of checkboxes
* based on the selection.
*/
function ajax_example_autocheckboxes($form, &$form_state) {
$default = !empty($form_state['values']['howmany_select']) ? $form_state['values']['howmany_select'] : 1;
$form['howmany_select'] = array(
'#title' => t('How many checkboxes do you want?'),
'#type' => 'select',
'#options' => array(1 => 1, 2 => 2, 3 => 3, 4 => 4),
'#default_value' => $default,
'#ajax' => array(
'callback' => 'ajax_example_autocheckboxes_callback',
'wrapper' => 'checkboxes-div',
'method' => 'replace',
'effect' => 'fade',
),
);
$form['checkboxes_fieldset'] = array(
'#title' => t("Generated Checkboxes"),
// The prefix/suffix provide the div that we're replacing, named by
// #ajax['wrapper'] above.
'#prefix' => '
'#suffix' => '
'#type' => 'fieldset',
'#description' => t('This is where we get automatically generated checkboxes'),
);
// Complete example below!
When the 'howmany_select' element is changed, a background request is issued to the server requesting that the form be rebuilt. After the form is rebuilt, using the changed 'howmany_select' field as input on how to rebuild it, the callback is called:
/**
* Callback element needs only select the portion of the form to be updated.
* Since #ajax['callback'] return can be HTML or a renderable array (or an
* array of commands), we can just return a piece of the form.
*/
function ajax_example_autocheckboxes_callback($form, $form_state) {
return $form['checkboxes_fieldset'];
}
The callback in this case (and in many cases) just selects the portion of the form which is to be replaced on the HTML page and returns it. That portion of the form is later rendered and returned to the page, where it replaces the #ajax['wrapper'] which was provided.
That's Ajax forms in a nutshell. A form element with the #ajax property submits a background request to the server when it is triggered by a click or change. The form gets rebuilt (on the server) by the form-builder function, and then the callback named in #ajax['callback'] is called, which selects the portion of the form to return for replacement on the original page.
In more detail
Here is the complete example discussed above, from the Ajax Examples in the Examples module. (This example is live and maintained. You can download the Examples modules and experiment with this example.)
/**
* Ajax-enabled select element causes replacement of a set of checkboxes
* based on the selection.
*/
function ajax_example_autocheckboxes($form, &$form_state) {
$default = !empty($form_state['values']['howmany_select']) ? $form_state['values']['howmany_select'] : 1;
$form['howmany_select'] = array(
'#title' => t('How many checkboxes do you want?'),
'#type' => 'select',
'#options' => array(1 => 1, 2 => 2, 3 => 3, 4 => 4),
'#default_value' => $default,
'#ajax' => array(
'callback' => 'ajax_example_autocheckboxes_callback',
'wrapper' => 'checkboxes-div',
'method' => 'replace',
'effect' => 'fade',
),
);
$form['checkboxes_fieldset'] = array(
'#title' => t("Generated Checkboxes"),
// The prefix/suffix provide the div that we're replacing, named by
// #ajax['wrapper'] above.
'#prefix' => '
'#suffix' => '
'#type' => 'fieldset',
'#description' => t('This is where we get automatically generated checkboxes'),
);
$num_checkboxes = !empty($form_state['values']['howmany_select']) ? $form_state['values']['howmany_select'] : 1;
for ($i = 1; $i <= $num_checkboxes; $i++) {
$form['checkboxes_fieldset']["checkbox$i"] = array(
'#type' => 'checkbox',
'#title' => "Checkbox $i",
);
}
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* Callback element needs only select the portion of the form to be updated.
* Since #ajax['callback'] return can be HTML or a renderable array (or an
* array of commands), we can just return a piece of the form.
*/
function ajax_example_autocheckboxes_callback($form, $form_state) {
return $form['checkboxes_fieldset'];
}The form is presented to the user, as any form would be.
In the form, a div with an HTML ID of 'checkboxes-div' wraps $form['checkboxes']. This is done with $form['checkboxes']['#prefix'] and $form['checkboxes']['#suffix'].
If the user changes $form['howmany_select'], a background request is made to the server, causing the form to be rebuilt.
The form is rebuilt, with as many checkboxes as were requested by $form['howmany_select'].
ajax_example_autocheckboxes_callback() is called. It selects the piece of the form which is to be replaced on the page (almost always the same as what's in #ajax['wrapper']).
The portion returned is rendered, sent back to the page, and the div with id 'checkboxes-div' is replaced on the page.
Details and Warnings
Changes to the form must only be made in the form builder function (ajax_example_autocheckboxes() in the example here), or validation will fail. The callback function must not alter the form or any other state.
About Ajax callbacks and #default_value: When Ajax replaces form elements on the page, the form field values are not automatically populated with #default_value. However, there are other ways to set default values when using Ajax callbacks. See these links for further discussion/hints and example code: Form API: default value does not change and Default_value not working for Radio Buttons in Ajax Callback.
It is possible to replace any HTML on the page, not just a form element. This is just a matter of providing a wrapper ID.
You can easily replace the entire form if that is easiest. Just add a #prefix and #suffix to the entire form array, then set that as the #ajax['wrapper']. (This will allow you to change multiple form elements via a single ajax call.) The only reason not to do this is that the process is faster if less information is transferred.
Keep in mind that the $form you're dealing with in your callback function has already been sent through all the form processing functions (but hasn't yet been sent to drupal_render()). So while adjusting, say, the markup of an element is straightforward:
$elements['some_element']['#markup'] = 'New markup.';
return $elements;
Changing a value that has already been converted into the #attributes property means digging deeper into the $form array, as well as also changing that element's corresponding property.
// You need to do both
$elements['some_element']['#disabled'] = TRUE;
$elements['some_element']['#attributes']['disabled'] = 'disabled';
return $elements;
Graceful degradation when the browser does not support JavaScript
It is considered best practice to provide graceful degradation of behaviors in the case the browser does not support JavaScript. Ajax forms are built for this, but it may take considerable effort to make a form behave correctly (and easily) in either a JavaScript or non-JavaScript environment. In most cases, a "next" button must be provided with the Ajax-enabled element. When it is pressed, the page (and form) are rebuilt as they are when the Ajax-enabled element is changed. The Examples module provides several examples of Ajax with graceful degradation in ajax_example_graceful_degradation.inc:
An add-more button
A dependent dropdown example
Dynamic sections
Wizard (classic multistep form)
More extensive Ajax features
The Ajax Framework provides many more features and options in addition to basic forms behavior.
Ajax Framework Commands may be used on the server side to generate dynamic behaviors on the page. In fact, the #ajax['callback'] function may return an array of these commands instead of returning a renderable array or an HTML string. These allow general dynamic page functions that go well beyond simple Form API operations. Views module, for example, makes heavy use of these in its user interface.
The #ajax['callback'] does not have to return a portion of the form. It can return any renderable array, or it can return an HTML string.
The replace method is the default and most common, but it is also possible to do other things with the content returned by the #ajax['callback'], including prepending, appending, etc.
It is possible to replace ajax_form_callback() with your own functions. If you do so, ajax_form_callback() would be the model for the replacement. In that case, you would change #ajax['path'] from the default 'system/ajax' and set up a menu entry in hook_menu() to point to your replacement path.
Additional resources
The Examples module provides the example given here, an Ajax-enabled dependent dropdown, and several other examples, including an example of graceful degradation when JavaScript is not enabled.
See the Ajax Framework documentation and the Form API Reference discussion of the #ajax property.