Last updated December 14, 2011. Created by rfay on March 24, 2010.
Edited by claudiu.cristea, csdco, larowlan, CrookedNumber. Log in to edit this page.
Introduction to AJAX-enabled Forms
AJAX-enabled forms in Drupal 7 offer dynamic form behavior with no page reloads and are easy to create and manipulate. They are a simple 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. Drupal does all the work for you.
- AJAX forms are a close relative of multistep forms.
- Most of the time, AJAX-enabled forms are just 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 just 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 code, so you don't have to think about it any more.
- 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 (a select, or a submit, or whatever)
- 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 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.
- The
- 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.
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']:
<?php
/**
* 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']) ? $form_state['values']['howmany'] : 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' => '<div id="checkboxes-div">',
'#suffix' => '</div>',
'#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' field as input on how to rebuild it, the callback is called:
<?php
/**
* 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.)
<?php
/**
* 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']) ? $form_state['values']['howmany'] : 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' => '<div id="checkboxes-div">',
'#suffix' => '</div>',
'#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'], 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'].
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' 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.
- 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
#prefixand#suffixto 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
$formyou'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:<?php
$elements['some_element']['#markup'] = 'New markup.';
return $elements;
?>
Changing a value that has already been converted into the#attributesproperty means digging deeper into the$formarray, as well as also changing that element's corresponding property.<?php
// 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 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
#ajaxproperty.
Comments
Backport?
Can anyone comment as to whether or not this was backported to Drupal 6? Or is this all evolved from the ctools implementation?
What I'm looking to do is to implement AJAX form validation in D6 while maintaining forward-compatibility with D7. Thoughts would be welcome.
--
Like us, follow us, or visit us!
No, none of this is backported
None of the AJAX forms stuff has been backported to D6, and I wouldn't expect it.
You may be interested in the AHAH Helper module, which makes D6 AHAH a little less painful.
Essentially, D7 is so much easier than D6 that the upgrade mostly means removing code from existing modules (if they were written correctly, with all the work being done in the form builder function)
There is *nothing* different about the concept of form validation. It is done by the Form API, and you let it do it. You don't fiddle with it in the callback or path, you don't change form elements any other way. That's all the same between D6 and D7.
Ajax validation
Is there an easy way to do no-page-reload validation? By easy i mean using '_validate' function and without mixing form builder with validation.
Błażej Owczarczyk
Why do "#ajax" for form
Why do "#ajax" for form elements submit, button, and image_button work differently? For example, if I want return portion of form like
return $form['checkboxes_fieldset'];, than it works only with "button" element.not defined
Sorry for beign stupied, but i cant get this to work,
Seems ['values']['howmay'] should be ['howmany_select']
Right?
Yes, it seems to me that it
Yes, it seems to me that it should.
Can ajax call back modify form on submit event?
It seems like ajax callback function do not work correctly if #type='button' and #type='submit'. The form is never rebuilt, the submit hooks never run. If the wrapper is the entire form Setting #markup does not work.
If wrapper is a form element of #type = 'markup', setting #markup for this element seems to work, how ever you can not modify $form. For example disable or hide some of the elements.
Test case
1) create a form
2) create an "error box" element of type 'markup'
3) create a submit button
4) add validation
generate following
case a) display form, form state, an error message in the "error_box", allowing user to resubmit
case b) replace the entire form with a message "thank you"
case c) instead of displaying message "thank you" redirect the user to a different node
more details can be found at http://drupal.org/node/1193378
"You can easily replace the
"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 is that the process is faster if less information is transferred."
If I do that, the AJAX handlers seem to get lost on the way. "AJAXified" elements loaded with through another #ajax element simply get ignored.
Does anybody know of a fix/workaround for that?
Resolved problem
I got things working, The ajax programming model in Drupal does not work the way I expected. I expected to do all my work in the ajax callback, not in the form building process. Here are the tricks I learned
- add #prefix and #suffix to the entire form
- create an ajax button set the wrapper to the #prefix id value
- do everything in the form build function hooks (_form_ajax)
- your actually ajax callback should just return the form,
In your build function
<?php
$user_submitted_form = array_key_exists('triggering_element', $form_state);
if ($user_submitted_form) {
$form = process_form_($form, $form_state);
} else {
$form = build_form_impl($form, $form_state);
}
In your process form if you want to display the form and some sort of error
//search for a <div id="accountInfo">
if (($found = strpos($page, "accountInfo")) === false) {
// rebuild the form so that user can change their details
$form = build_form_impl($form, $form_state);
$form['results']['#markup'] = "<div style='border-style:solid;border-color:red;padding:5px;'><h2>Error</h2> $page</div>";
return $form;
}
?>
If you do not want the form displayed
<?php
$form['loginId'] = array('#type' => 'hidden');
unset($form['password1']);
unset($form['password2']);
unset($form['email1']);
unset($form['email2']);
unset($form['submit']);
$form['my_results']['#markup'] = $page;
return $form;
?>
Nice
That did help to clear things up a bit. Thanks a lot!
This definitely works, but
This definitely works, but any other ajax functionality that I have running on page load doesn't do its job the second time around and all of the css id's in the form have a new name, thus my re-loaded form looks quite different... Any suggestions from anyone on alternatives to simply insert a new form elements?
I misunderstood his solution.
I misunderstood his solution. If you do things properly the returned form will have the new elements, #ajax will still work, and you won't be getting incremented IDs on your form objects. Here's some sample code:
<?php
function my_form_builder($form, &$form_state) {
$form['#prefix'] = '<div id="my-form-wrapper">';
$form['#suffix'] = '</div>';
$form['first_name'] = array(
'#title' => 'Your First Name',
'#type' => 'textfield',
'#ajax' => array(
'callback' => 'my_form_ajax_callback',
'wrapper' => 'my-form-wrapper',
),
);
if (strlen($form_state['values']['first_name']) > 0) {
// Here you can add a new form element based on the current values a user entered.
$form['last_name'] = array(
'#title' => 'Your Last Name',
'#type' => 'textfield',
'#description' => 'You entered your first name, now enter your last name.',
);
}
}
function my_form_ajax_callback($form, &$form_state) {
return $form;
}
?>
Would be nice to get ajax
Would be nice to get ajax submit documentation on here.
Custom field formatter with AJAX
Hi guys,
I'm building my own custom field - and i am trying to get the field formatter using AJAX :
My field formatters has several fields including a button, which on click should
1/ Call a votingapi function to cast a voe
2/ Update a markup field in my page to take into account the new vote
My field formatter looks like this :
function vote_field_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
if ($display['type'] == 'vote_field_formatter_default') {
list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
$settings = $display['settings'];
$values = array(
'entity_type' => $entity_type,
'entity_id' => $id,
'tag' => $settings['tag'],
);
$element = array();
$element[0] = drupal_get_form('vote_field_custom_widget', $values, $settings);
return $element;
}
}
Say i have the following fields in my form_builder function "vote_field_custom_widget":
//I simplified the code here - Check if this is a form callback
if(array_key_exists('triggering_element', $form_state)) {
votingapi_set_vote(....);
}
$current_score = votingapi_get_scores(...);
$form['score_display'] = array(
'#type' => 'item',
'#title' => t('Score'),
'#markup' => check_plain($current_score),
'#prefix' => '<div id="score-display-div">',
'#suffix' => '</div>',
);
//Check if the user has ever voted for this entity
$form['vote_up_button'] = array(
'#type' => 'image_button',
'#value' => t('Vote'),
'#src' => drupal_get_path('module', 'vote_field') . '/images/up.png',
'#ajax'=> array(
'callback' => 'vote_field_vote_callback',
'wrapper' => 'score-display-div',
'method' => 'change',
'effect' => 'fade',
),
);
And my ajax callback looks like this :
function vote_field_vote_callback($form, $form_state) {return $form['score_display'];
}
The problem i have is that I think the AJAX will rebuild the form and the field formatter by calling vote_field_field_formatter_view, and hence, call the drupal_get_form. After checking at the drupal_get_form, i noticed that it creates a new $form and $form_state everytime so i don't have access to the $form_state[' triggering_element'] to check in my field_formatter function.
I am probably missing a step somewhere where i could have the new $form_state at the field formatter level ...
Anybody can help ?
How to re-create ajaxed form
Hi,
I created form that show additional textfields when you check checkbox (you can see it below).
My first question: I found it working only if I wrap and refresh whole form. How can I refresh only these two textfield without using special field set. I tried with markup but with no luck.
My second question: Value of checkbox is stored in v_email_smtp_server_auth variable. When I open again these form check box is already checked but folowing textfields are not show as ajax is no executed yet. How to resolve these?
$form['email']['v_email_smtp_server_auth'] = array(
'#type' => 'checkbox',
'#title' => t('Authentication required'),
'#default_value' => variable_get('v_email_smtp_server_auth', 0),
'#description' => t('Check these option if your server need login authentication.'),
'#ajax' => array(
'callback' => '_v_ajax_admin_settings_form_callback',
'wrapper' => 'v-admin-settings-form',
'effect' => 'fade',
'progress' => array('type' => 'none'),
),
);
if (!empty($form_state['values']['v_email_smtp_server_auth']) && $form_state['values']['v_email_smtp_server_auth']) {
$form['email']['v_email_smtp_username'] = array(
'#type' => 'textfield',
'#title' => t('Username'),
'#default_value' => variable_get('v_email_smtp_username', ''),
'#size' => 30,
'#maxlength' => 50,
'#required' => TRUE,
'#description' => t('Username for SMTP server')
);
$form['email']['v_email_smtp_password'] = array(
'#type' => 'password',
'#title' => t('Password'),
'#default_value' => variable_get('v_email_smtp_password', ''),
'#size' => 30,
'#maxlength' => 50,
'#required' => TRUE,
'#description' => t('Password for SMTP server')
);
}
Thanks!
--
Aleš :)
An error.
Drupal 7 in #type = password cannot have #default_value; how you solve this problem?
Yessir!
I believe that's exactly the snippet & quick explanation I been looking for, I'll give that a try to print "auto-updating" node edit forms (for each result returned in my draggable view), I have it working now using iframes but it's sooo stupid to bootstrap more then once like that to achieve multiple node edit forms on one page (view in my case). I'll let ya know if I get stuck, thanks for the tip yo.
There's another page
There's another page discussing this here: https://drupal.org/node/348475
I get an error when tried ajax_example
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...
getting started
I can not even find what this AJAX-enabled Forms Example does. After installing and enabling, the module does not create a new content type, nor a new block, nor new content...
How do I even get started with this Ajax-form?
Thanks (and sorry for asking such a silly question ...)
I wouldn't expect to find a
I wouldn't expect to find a new content type or new block or anything like that. Look in the hook_menu() function in the Forms Example module and see what path the new form resides at. For example, I just opened the file form_example.module and can see this code:
<?php$items = array();
$items['examples/form_example'] = array(
'title' => 'Form Example',
'page callback' => 'form_example_intro',
'access callback' => TRUE,
'expanded' => TRUE,
);
?>
Which tells me that by going to http://www.example.com/examples/form_example I can see this particular function (form_example_intro) in action.
Thanks.
Thanks. Got it.
Problems with form_set_error()
I have build a big ajax-multistep-form successfully with help from this ajax-example (Examples module).
On the last step is some validation.
What happens:
The reason:
If you are inside the multiform any message produced by form_set_error() is placed inside the #ajax-div so it disappears after correcting the input at the next ajax-request.
BUT the last page has a classic submit Button - with no '#ajax'-Callbacks. This Button has a different behaviour.
In this case a message produced by form_set_error() is placed outside the #ajax-div because there is a real page-reload.
My question
What can I do to have only one place for error-messages ?
Because the submit ends with a drupal_goto(), this Button cannot be ajax. This would cause a big ajax-error: the return-value is not the expected $form.
ajax dependent dropdowns inside a (user register) form_alter
Hi everyone, does anyone know how to integrate a hierarchical select inside a form_alter (in my case a user register) and make it work? In my case, the dropdowns don't react to each other and I've read it won't work unless I adapt it... but I couldn't read what 's the solution. Anyone?
thanks a lot
PIÑA&POLLO
Problems with form_set_error() - Workaround
A Javascript-Solution.
Idea: On "Submit" remove the error-msg above the FORM.
But: The default-event on AJAX-buttons is "mousedown" - not "click". I dislike this behaviour - and it's possible to change this in the form:
'#ajax' => array('wrapper' => 'wizard-form-wrapper',
'callback' => 'ubg_multiform_wizard_callback',
'event' => 'click', // not mousedown
),
Then this small js removes the error-msg if the user clicks on a prev/next Button:
(function ($) {Drupal.behaviors.ubg_multiform = {
attach: function (context) {
$('input.form-submit.ajax-processed', context).click(function(){
$('#content-header .messages').removeClass('messages error').empty();
});
}
};
})(jQuery);