* Set up calls to drupal_get_form() for all our example cases.
*
* Implements hook_menu().
+ * See @link menu_example.module Menu Example @endlink for more details on
+ * hook_menu().
*/
function ajax_example_menu() {
$items = array();
'access callback' => TRUE,
'expanded' => TRUE,
);
+
+ // Change the description of a form element.
$items['examples/ajax_example/simplest'] = array(
'title' => 'Simplest AJAX Example',
'page callback' => 'drupal_get_form',
'access callback' => TRUE,
'weight' => 0,
);
- // Automatically generate checkboxes
+ // Generate a changing number of checkboxes.
$items['examples/ajax_example/autocheckboxes'] = array(
'title' => 'Generate checkboxes',
'page callback' => 'drupal_get_form',
'access callback' => TRUE,
'weight' => 1,
);
-
- // Automatically generate textfields
+ // Generate different textfields based on form state.
$items['examples/ajax_example/autotextfields'] = array(
'title' => 'Generate textfields',
'page callback' => 'drupal_get_form',
'weight' => 2,
);
+ // Submit a form without a page reload.
$items['examples/ajax_example/submit_driven_ajax'] = array(
'title' => 'Submit-driven AJAX',
'page callback' => 'drupal_get_form',
'weight' => 3,
);
+ // Repopulate a dropdown based on form state.
$items['examples/ajax_example/dependent_dropdown'] = array(
'title' => 'Dependent dropdown',
'page callback' => 'drupal_get_form',
'access callback' => TRUE,
'weight' => 4,
);
+ // Repopulate a dropdown, but this time with graceful degredation.
+ // See ajax_example_graceful_degradation.inc.
$items['examples/ajax_example/dependent_dropdown_degrades'] = array(
'title' => 'Dependent dropdown (with graceful degradation)',
'page callback' => 'drupal_get_form',
'weight' => 5,
'file' => 'ajax_example_graceful_degradation.inc',
);
+ // The above example as it appears to users with no javascript.
$items['examples/ajax_example/dependent_dropdown_degrades_no_js'] = array(
'title' => 'Dependent dropdown with javascript off',
'page callback' => 'drupal_get_form',
'weight' => 5,
);
+ // Populate a form section based on input in another element.
$items['examples/ajax_example/dynamic_sections'] = array(
'title' => 'Dynamic Sections (with graceful degradation)',
'page callback' => 'drupal_get_form',
'weight' => 6,
'file' => 'ajax_example_graceful_degradation.inc',
);
+ // The above example as it appears to users with no javascript.
$items['ajax_example/dynamic_sections_no_js'] = array(
'title' => 'Dynamic Sections w/JS turned off',
'page callback' => 'drupal_get_form',
'file' => 'ajax_example_graceful_degradation.inc',
);
-
+ // A classic multi-step wizard, but with no page reloads.
+ // See ajax_example_graceful_degradation.inc.
$items['examples/ajax_example/wizard'] = array(
'title' => 'Wizard (with graceful degradation)',
'page callback' => 'drupal_get_form',
'file' => 'ajax_example_graceful_degradation.inc',
'weight' => 7,
);
+ // The above example as it appears to users with no javascript.
$items['examples/ajax_example/wizard_no_js'] = array(
'title' => 'Wizard w/JS turned off',
'page callback' => 'drupal_get_form',
'weight' => 7,
);
+ // Add-more button that creates additional form elements.
+ // See ajax_example_graceful_degradation.inc.
$items['examples/ajax_example/add_more'] = array(
'title' => 'Add-more button (with graceful degradation)',
'page callback' => 'drupal_get_form',
'file' => 'ajax_example_graceful_degradation.inc',
'weight' => 8,
);
+ // The above example as it appears to users with no javascript.
$items['examples/ajax_example/add_more_no_js'] = array(
'title' => 'Add-more button w/JS turned off',
'page callback' => 'drupal_get_form',
'weight' => 8,
);
- $items['examples/ajax_example/advanced_commands'] = array(
- 'title' => 'AJAX framework commands',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('ajax_example_advanced_commands'),
- 'access callback' => TRUE,
- 'file' => 'ajax_example_advanced.inc',
- 'weight' => 10,
- );
-
+ // Use the AJAX framework outside the context of a form.
+ // See ajax_example_misc.inc.
$items['examples/ajax_example/ajax_link'] = array(
'title' => 'Ajax Link',
'page callback' => 'ajax_example_render_link',
'file' => 'ajax_example_misc.inc',
'weight' => 9,
);
-
+ // A menu callback is required when using ajax outside of the Form API.
$items['ajax_link_callback'] = array(
'page callback' => 'ajax_link_response',
'access callback' => 'user_access',
'file' => 'ajax_example_misc.inc',
);
+ // Use AJAX framework commands outside of the #ajax form property.
+ // See ajax_example_advanced.inc.
+ $items['examples/ajax_example/advanced_commands'] = array(
+ 'title' => 'AJAX framework commands',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('ajax_example_advanced_commands'),
+ 'access callback' => TRUE,
+ 'file' => 'ajax_example_advanced.inc',
+ 'weight' => 10,
+ );
+
return $items;
}
/**
* Simple form whose ajax-enabled 'changethis' member causes a text change
* in the description of the 'replace_textfield' member.
+ * See @link http://drupal.org/node/262422 Form API Tutorial @endlink
*/
function ajax_example_simplest($form, &$form_state) {
$form = array();
'three' => 'three',
),
'#ajax' => array(
+ // #ajax has two required keys: callback and wrapper.
+ // 'callback' is a function that will be called when this element changes.
'callback' => 'ajax_example_simplest_callback',
+ // 'wrapper' is the HTML id of the page element that will be replaced.
'wrapper' => 'replace_textfield_div',
+ // There are also several optional keys - see ajax_example_autocheckboxes
+ // below for details on 'method', 'effect' and 'speed' and
+ // ajax_example_dependent_dropdown for 'event'.
),
);
- // This entire form element will be replaced with an updated value.
- // However, it has to have the prefix/suffix to work right, as the entire
- // div is replaced.
- // In this example, the description is dynamically updated during form
- // rebuild.
-
+ // This entire form element will be replaced whenever 'changethis' is updated.
$form['replace_textfield'] = array(
'#type' => 'textfield',
'#title' => t("Why"),
+ // The prefix/suffix provide the div that we're replacing, named by
+ // #ajax['wrapper'] above.
'#prefix' => '<div id="replace_textfield_div">',
'#suffix' => '</div>',
);
+ // An AJAX request calls the form builder function for every change.
+ // We can change how we build the form based on $form_state.
if (!empty($form_state['values']['changethis'])) {
$form['replace_textfield']['#description'] = t("Say why you chose") . " '{$form_state['values']['changethis']}'";
}
return $form;
}
+
/**
* Callback for ajax_example_simplest.
*
- * The form item 'replace_textfield' has already been processed and changed
- * when the form was submitted and rebuilt during the AJAX call, so now
- * we just return the piece of the form that changed.
+ * On an ajax submit, the form builder function is called again, then the $form
+ * and $form_state are passed to this callback function so it can select which
+ * portion of the form to send on to the client.
+ *
+ * @return renderable array (the textfield element)
*/
function ajax_example_simplest_callback($form, $form_state) {
// The form has already been submitted and updated. We can return the replaced
return $form['replace_textfield'];
}
-
/**
* 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;
+ // Since the form builder is called after every AJAX request, we rebuild
+ // the form based on $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?'),
'#ajax' => array(
'callback' => 'ajax_example_autocheckboxes_callback',
'wrapper' => 'checkboxes-div',
- 'method' => 'replace',
- 'effect' => 'fade',
+ //'method' defaults to replaceWith, but valid values also include
+ // append, prepend, before and after.
+ 'method' => 'replaceWith',
+ // 'effect' defaults to none. Other valid values are 'fade' and 'slide'.
+ // See ajax_example_autotextfields for an example of 'fade'.
+ 'effect' => 'slide',
+ // 'speed' defaults to 'slow'. You can also use 'fast'
+ // or a number of milliseconds for the animation to last.
+ 'speed' => 'slow',
),
-
);
* 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.
+ * See @link ajax_example_advanced.inc AJAX Advanced Commands for more details
+ * on AJAX framework commands.
+ *
+ * @return renderable array (the checkboxes fieldset)
*/
function ajax_example_autocheckboxes_callback($form, $form_state) {
return $form['checkboxes_fieldset'];
}
+
/**
* Show/hide textfields based on AJAX-enabled checkbox clicks.
*/
'callback' => 'ajax_example_autotextfields_callback',
'wrapper' => 'textfields',
'effect' => 'fade',
-
),
);
'#description' => t('This is where we put automatically generated textfields'),
);
- if (!empty($form_state['values']['ask_first_name']) && $form_state['values']['ask_first_name']) {
+ // Since checkboxes return TRUE or FALSE, we have to check that
+ // $form_state has been filled as well as what it contains.
+ if (!empty($form_state['values']['ask_first_name']) && $form_state['values']['ask_first_name']) {
$form['textfields']['first_name'] = array(
'#type' => 'textfield',
'#title' => t('First Name'),
);
}
-
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Click Me'),
);
-
return $form;
}
-
/**
* Selects the piece of the form we want to use as replacement text and returns
* it as a form (renderable array).
/**
* Select the 'box' element, change the markup in it, and return it as a
* renderable array.
+ *
+ * @return renderable array (the box element)
*/
function ajax_example_submit_driven_callback($form, $form_state) {
+ // In most cases, it is recomended that you put this logic in form generation
+ // rather than the callback. Submit driven forms are an exception, because
+ // you may not want to return the form at all.
$element = $form['box'];
$element['#markup'] = "Clicked submit ({$form_state['values']['op']}): " . date('c');
return $element;
* are updated.
*/
function ajax_example_dependent_dropdown($form, &$form_state) {
- // get the list of options to populate the first dropdown
+ // Get the list of options to populate the first dropdown.
$options_first = _ajax_example_get_first_dropdown_options();
- // if we have a value for the first dropdown from $form_state['values'] we use
+ // If we have a value for the first dropdown from $form_state['values'] we use
// this both as the default value for the first dropdown and also as a
// parameter to pass to the function that retrieves the options for the
// second dropdown.
'#title' => 'First Dropdown',
'#options' => $options_first,
'#default_value' => $selected,
- // bind an ajax callback to the change event (which is the default for the
+ // Bind an ajax callback to the change event (which is the default for the
// select form type) of the first dropdown. It will replace the second
// dropdown when rebuilt
'#ajax' => array(
+ // When 'event' occurs, Drupal will perform an ajax request in the
+ // background. Usually the default value is sufficient (eg. change for
+ // select elements), but valid values include any jQuery event,
+ // most notably 'mousedown', 'blur', and 'submit'.
+ 'event' => 'change',
'callback' => 'ajax_example_dependent_dropdown_callback',
'wrapper' => 'dropdown_second_replace',
),
'#type' => 'submit',
'#value' => t('Submit'),
);
-
return $form;
}
/**
* Selects just the second dropdown to be returned for re-rendering
*
- * The version here has been re-loaded with a different set of options and
- * is sent back to the page to be updated.
+ * Since the controlling logic for populating the form is in the form builder
+ * function, all we do here is select the element and return it to be updated.
*
* @return renderable array (the second dropdown)
*/
return $form['dropdown_second'];
}
-
/**
* Helper function to populate the first dropdown. This would normally be
* pulling data from the database.
* available.
*
* In each of these the key idea is that the form is rebuilt different ways
- * depending on form input. So the formbuilder function is in charge of
- * almost all logic.
+ * depending on form input. In order to accomplish that, the formbuilder function
+ * is in charge of almost all logic.
*
* @see ajax
*
* @}
-*/
+ */
* if javascript is not enabled. The Javascript snippet is really only used
* to enable us to present the form in degraded mode without forcing the user
* to turn off Javascript. Both of these are loaded by using the
- * #attached FAPI property, so it's a good example of how to use that.
+ * #attached FAPI property, so it is a good example of how to use that.
*
* The extra argument $no_js_use is here only to allow presentation of this
* form as if Javascript were not enabled. ajax_example_menu() provides two
* @ingroup ajax_degradation_examples
*/
function ajax_example_dependent_dropdown_degrades($form, &$form_state, $no_js_use = FALSE) {
- // get the list of options to populate the first dropdown
+ // Get the list of options to populate the first dropdown.
$options_first = _ajax_example_get_first_dropdown_options();
- // if we have a value for the first dropdown from $form_state['values'] we use
+ // If we have a value for the first dropdown from $form_state['values'] we use
// this both as the default value for the first dropdown and also as a
// parameter to pass to the function that retrieves the options for the
// second dropdown.
'#options' => $options_first,
'#attributes' => array('class' => array('enabled-for-ajax')),
- // bind an ajax callback to the change event (which is the default for the
- // select form type) of the first dropdown. It will replace the second
- // dropdown when rebuilt.
+ // The '#ajax' property allows us to bind a callback to the server whenever this
+ // form element changes. See ajax_example_autocheckboxes and
+ // ajax_example_dependent_dropdown in ajax_example.module for more details.
'#ajax' => array(
'callback' => 'ajax_example_dependent_dropdown_degrades_first_callback',
'wrapper' => 'dropdown-second-replace',
unset($form['dropdown_first_fieldset']['dropdown_first']['#ajax']);
}
- // The CSS for this module hides this next button if JS is enabled.
+ // Since we don't know if the user has js or not, we always need to output
+ // this element, then hide it with with css if javascript is enabled.
$form['dropdown_first_fieldset']['continue_to_second'] = array(
'#type' => 'submit',
'#value' => t('Choose'),
'#prefix' => '<div id="dropdown-second-replace">',
'#suffix' => '</div>',
'#attributes' => array('class' => array('enabled-for-ajax')),
-
- // when the form is rebuilt during processing (either AJAX or multistep),
+ // When the form is rebuilt during processing (either AJAX or multistep),
// the $selected variable will now have the new value and so the options
// will change.
'#options' => _ajax_example_get_second_dropdown_options($selected),
);
-
$form['dropdown_second_fieldset']['submit'] = array(
'#type' => 'submit',
'#value' => t('OK'),
-
// This class allows attached js file to override the disabled attribute,
// since it's not necessary in ajax-enabled form.
'#attributes' => array('class' => array('enabled-for-ajax')),
$form['dropdown_second_fieldset']['submit']['#disabled'] = TRUE;
}
-
return $form;
}
drupal_set_message(t('Your values have been submitted. dropdown_first=@first, dropdown_second=@second', array('@first' => $form_state['values']['dropdown_first'], '@second' => $form_state['values']['dropdown_second'])));
return;
}
-
// 'Choose' or anything else will cause rebuild of the form and present
// it again.
$form_state['rebuild'] = TRUE;
}
-
/**
* Example of a form with portions dynamically enabled or disabled, but
* with graceful degradation in the case of no javascript.
// Attach the CSS and JS we need to show this with and without javascript.
// Without javascript we need an extra "Choose" button, and this is
// hidden when we have javascript enabled.
-
$form['#attached']['css'] = array(
drupal_get_path('module', 'ajax_example') . '/ajax_example.css',
);
Try the <a href="!ajax_link">AJAX version</a> and the <a href="!non_ajax_link">simulated-non-AJAX version</a>.
', array('!ajax_link' => url('ajax_example/dynamic_sections'), '!non_ajax_link' => url('ajax_example/dynamic_sections_no_js') )) . '</div>',
);
-
$form['question_type_select'] = array(
'#type' => 'select',
'#title' => t('Question style'),
// actually turning off javascript in the browser. Removing the #ajax
// element turns off AJAX behaviors on that element and as a result
// ajax.js doesn't get loaded.
-
if ($no_js_use) {
// Remove the #ajax from the above, so ajax.js won't be loaded.
unset($form['question_type_select']['#ajax']);
// that gets rebuilt.
$form['questions_fieldset'] = array(
'#type' => 'fieldset',
-
// These provide the wrapper referred to in #ajax['wrapper'] above.
'#prefix' => '<div id="questions-fieldset-wrapper">',
'#suffix' => '</div>',
'#description' => t('Please type the correct answer to the question.'),
);
break;
-
}
$form['questions_fieldset']['submit'] = array(
'#value' => t('Submit your answer'),
);
}
-
return $form;
}
/**
* Validation function for ajax_example_dynamic_sections().
- *
*/
function ajax_example_dynamic_sections_validate($form, &$form_state) {
$answer = $form_state['values']['question'];
// This is only executed when a button is pressed, not when the AJAXified
// select is changed.
// Now handle the case of the next, previous, and submit buttons.
- // only submit will result in actual submission, all others rebuild.
+ // Only submit will result in actual submission, all others rebuild.
switch($form_state['triggering_element']['#value']) {
case t('Submit your answer'): // Submit: We're done.
$form_state['rebuild'] = FALSE;
* This example is a classic wizard, where a different and sequential form
* is presented on each step of the form.
*
- * In the AJAX version, form is replaced for each wizard section. In the
+ * In the AJAX version, the form is replaced for each wizard section. In the
* multistep version, it causes a new page load.
*
* @param $form
function ajax_example_wizard($form, &$form_state, $no_js_use = FALSE) {
// Provide a wrapper around the entire form, since we'll replace the whole
- // thing.
+ // thing with each submit.
$form['#prefix'] = '<div id="wizard-form-wrapper">';
$form['#suffix'] = '</div>';
$form['#tree'] = TRUE; // We want to deal with hierarchical form values.
. '</div>',
);
+ // $form_state['storage'] has no specific drupal meaning, but it is
+ // traditional to keep variables for multistep forms there.
$step = empty($form_state['storage']['step']) ? 1 : $form_state['storage']['step'];
$form_state['storage']['step'] = $step;
'#required' => TRUE,
);
break;
+
case 3:
$form['step3'] = array(
'#type' => 'fieldset',
'wrapper' => 'wizard-form-wrapper',
'callback' => 'ajax_example_wizard_callback',
),
-
);
}
if ($step > 1) {
// Save away the current information.
$current_step = 'step' . $form_state['storage']['step'];
- $form_state['storage']['values'][$current_step] = $form_state['values'][$current_step];
+ if (!empty($form_state['values'][$current_step])) {
+ $form_state['storage']['values'][$current_step] = $form_state['values'][$current_step];
+ }
// Increment or decrement the step as needed. Recover values if they exist.
if ($form_state['triggering_element']['#value'] == t('Next step')) {
$form_state['storage']['step']++;
- // If values had already been entered for this step, recover them from
+ // If values have already been entered for this step, recover them from
// $form_state['storage'] to pre-populate them.
$step_name = 'step' . $form_state['storage']['step'];
if (!empty($form_state['storage']['values'][$step_name])) {
}
// Otherwise, we still have work to do.
-
$form_state['rebuild'] = TRUE;
}
-//// AJAX add-more example, with graceful degradation.
-
/**
* This example shows a button to "add more" - add another textfield, and
* the corresponding "remove" button.
*/
function ajax_example_add_more($form, &$form_state, $no_js_use = FALSE) {
-
$form['description'] = array(
'#markup' => '<div>' . t('This example shows an add-more and a remove-last button. The <a href="!ajax">AJAX version</a> does it without page reloads; the <a href="!multistep">non-js version</a> is the same code but simulates a non-javascript environment, showing it with page reloads.',
array('!ajax' => url('examples/ajax_example/add_more'), '!multistep' => url('examples/ajax_example/add_more_no_js')))
// Because we have many fields with the same values, we have to set
// #tree to be able to access them.
$form['#tree'] = TRUE;
-
$form['names_fieldset'] = array(
'#type' => 'fieldset',
'#title' => t('People coming to the picnic'),
'#type' => 'submit',
'#value' => t('Add one more'),
'#submit' => array('ajax_example_add_more_add_one'),
+ // See the examples in ajax_example.module for more details on the
+ // properties of #ajax.
'#ajax' => array(
'callback' => 'ajax_example_add_more_callback',
'wrapper' => 'names-fieldset-wrapper',
'#value' => t('Submit'),
);
-
// This simply allows us to demonstrate no-javascript use without
// actually turning off javascript in the browser. Removing the #ajax
// element turns off AJAX behaviors on that element and as a result
// ajax.js doesn't get loaded.
- // For demonstration only! You don't need this. It's just so you don't
- // have to turn off javascript in your browser to test.
+ // For demonstration only! You don't need this.
if ($no_js_use) {
// Remove the #ajax from the above, so ajax.js won't be loaded.
- // For demonstration only.
if (!empty($form['names_fieldset']['remove_name']['#ajax'])) {
unset($form['names_fieldset']['remove_name']['#ajax']);
}
/**
* Submit handler for the "add-one-more" button.
*
- * It just increments the max counter and then causes a rebuild.
+ * It just increments the max counter and causes a rebuild.
*/
function ajax_example_add_more_add_one($form, &$form_state) {
$form_state['num_names']++;
/**
* Final submit handler.
*
- * Reports to us what values were finally set.
+ * Reports what values were finally set.
*/
function ajax_example_add_more_submit($form, &$form_state) {
$output = t('These people are coming to the picnic: @names',