AJAX

AHAH Changes for Drupal 7 (It's now called #AJAX)

Drupal 7 AHAH has changed its name to AJAX to use a more familiar term and to be slightly more trendy. A version of this presentation for D7 AJAX is at http://randyfay.com/ajax. D7 AJAX handling is massively simplified.

The AJAX Example for D7 is now in the Examples Module and will be maintained there. If you find AJAX features that you'd like to see in it that aren't there, file a request or submit a patch.

Topics: 

30 Seconds on the Drupal Form API

It's pretty easy to forget all the details of the Drupal Forms API, so we'll stop for a minute for a very short review.

(This one is at ahah_demo/simple_form.)

You add a form to your page like this:

<?php
$output
.= drupal_get_form('ahah_demo_simplest_form');
?>

Often that's done right from the hook_menu implementation.

The layout of a form is as below:

<?php
/**
 * A Hello-world for Forms API (FAPI)
 */
function ajax_demo_simplest_form($form, &$form_state) {

 
$form['explanation'] = array(
   
'#type' => 'markup',
   
'#value' => '<div>' . t('This is a basic form with just a bit of information') . '</div>',
  );

 
$form['experience'] = array(
   
'#type' => 'select',
   
'#title' => t('What is your experience level in Drupal?'),
   
'#options' => array(
     
'expert' => t('Expert'),
     
'journeyman' => t('Journeyman'),
     
'beginner' => t('Beginner')),
  );
 
$form['more'] = array(
   
'#type' => 'textfield',
   
'#title' => t("Say more about yourself"),
   
'#description' => t('Surely one word can\'t describe you.'),
  );
 
$form['submit'] = array(
   
'#type' => 'submit',
   
'#value' => t('Click Me'),
  );

  return
$form;
}


function
ajax_demo_simplest_form_submit($form, &$form_state) {
 
drupal_set_message(t('You have claimed to be %experience: %more',
    array(
     
'%experience'=>$form_state['values']['experience'],
     
'%more' => $form_state['values']['more'],
    )
  ));
}
?>

Topics: 

AJAX Simplest Example

The essence of AJAX forms is that you mark an element of a form as the one that activates AJAX behavior, and you tell it what section of the HTML is to be replaced when it's activated.

The form we're using here has a textfield with a description, which is just plain HTML. It has a prefix and suffix that mention its CSS ID. The CSS ID will be used by the javascript running in the background to determine what's to be replaced.

$form['changethis'] is marked as AJAX-enabled with the "#ajax" marker, which tells the Form API its callback function and the HTML ID (wrapper) to replace.

When $form['changethis'] is changed, javascript provided by Drupal makes a request to the server, which rebuilds the form and calls the callback, which selects a piece of the form. The resulting form piece is rendered and returned to the page, and is replaced by the javascript that made the original call.

(This one is live at http://d7.drupalexamples.info/examples/ajax_example/simplest and the code is on api.drupal.org at http://api.drupal.org/api/function/ajax_example_simplest/7)

Here's an example of the very simplest AJAX behavior from the Examples module.

<?php
/<strong>
 *
Simple form whose ajax-enabled 'changethis' member causes a text change
 
* in the description of the 'replace_textfield' member.
 */
function
ajax_example_simplest($form, &$form_state) {
 
$form = array();
 
$form['changethis'] = array(
   
'#title' => t("Choose something and explain why"),
   
'#type' => 'select',
   
'#options' => array(
     
'one' => 'one',
     
'two' => 'two',
     
'three' => 'three',
    ),
   
'#ajax' => array(
     
'callback' => 'ajax_example_simplest_callback',
     
'wrapper' => 'replace_textfield_div',
     ),
  );

 
// 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.

 
$form['replace_textfield'] = array(
   
'#type' => 'textfield',
   
'#title' => t("Why"),
   
'#prefix' => '<div id="replace_textfield_div">',
   
'#suffix' => '</div>',
  );

  if (!empty(
$form_state['values']['changethis'])) {
   
$form['replace_textfield']['#description'] = t("Say why you chose") .  " '{$form_state['values']['changethis']}'";
  }
  return
$form;
}
/</
strong>
 *
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.
 */
function
ajax_example_simplest_callback($form, $form_state) {
 
// The form has already been submitted and updated. We can return the replaced
  // item as it is.
 
return $form['replace_textfield'];
}
?>

Topics: 

AJAX Example: Select control and generated checkboxes

In this example we use a select control to determine how many checkboxes are generated.

(This one is live at http://d7.drupalexamples.info/examples/ajax_example/autocheckboxes and the code on api.drupal.org at http://api.drupal.org/api/function/ajax_example_autocheckboxes/7)

<?php
/<strong>
 *
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;
}

/</
strong>
 *
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'];
}
?>

Topics: 

AJAX Form Resources

Topics: 

AJAX Example: Textfields driven by checkboxes

In this example, we just use checkboxes to determine whether textboxes are displayed.

One of the fundamental ideas of having a form change based on selections within the form is that the form is reconfiguring itself based on $form_state. So here, the generation of the form is driven by $form_state['values']. If the checkbox for last name is checked, then we generate a textfield for last name.

(Experiment with this one at http://d7.drupalexamples.info/examples/ajax_example/autotextfields or see the current code at http://api.drupal.org/api/function/ajax_example_autotextfields/7.)

<?php
/<strong>
 *
Show/hide textfields based on AJAX-enabled checkbox clicks.
 */
function
ajax_example_autotextfields($form, &$form_state) {

 
$form['ask_first_name'] = array(
   
'#type' => 'checkbox',
   
'#title' => t('Ask me my first name'),
   
'#ajax' => array(
     
'callback' => 'ajax_example_autotextfields_callback',
     
'wrapper' => 'textfields',
     
'effect' => 'fade',
    )
  );
 
$form['ask_last_name'] = array(
  
'#type' => 'checkbox',
  
'#title' => t('Ask me my last name'),
   
'#ajax' => array(
     
'callback' => 'ajax_example_autotextfields_callback',
     
'wrapper' => 'textfields',
     
'effect' => 'fade',

    ),
  );

 
$form['textfields'] = array(
   
'#title' => t("Generated text fields for first and last name"),
   
'#prefix' => '<div id="textfields">',
   
'#suffix' => '</div>',
   
'#type' => 'fieldset',
   
'#description' => t('This is where we put automatically generated textfields'),
  );

   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'),
    );
  }
  if (!empty(
$form_state['values']['ask_last_name']) && $form_state['values']['ask_last_name']) {
   
$form['textfields']['last_name'] = array(
     
'#type' => 'textfield',
     
'#title' => t('Last Name'),
    );
  }


 
$form['submit'] = array(
   
'#type' => 'submit',
   
'#value' => t('Click Me'),
  );


  return
$form;
}


/</
strong>
 *
Selects the piece of the form we want to use as replacement text and returns
 
* it as a form (renderable array).
 *
 * @return
renderable array (the textfields element)
 */
function
ajax_example_autotextfields_callback($form, $form_state) {
  return
$form['textfields'];
}
?>

Topics: 

Form API Changes for Drupal 7, Part 2: AJAX/AHAH Changes

Continuing from Form API Changes for Drupal 7, Part 1: $form_state changes.

One of the many pleasant improvements to the Drupal Form API is that AJAX Forms are now really easy. Really easy. We used to call them AHAH forms, and the concept was the same, but they were so complicated that few people attempted them. In D7 we even changed the name to distance ourselves from "AHAH" :-)

In Drupal 6 you had to make a menu entry in hook_menu() that pointed to an AJAX callback handler that was complete black magic. (See a sanitized example.) People did this part differently, and it was hard to figure out the right way. (If you're doing D6 AHAH, the gory details and resources are here.)

In Drupal 7 there is none of that. But we still have to start with the basics so that you'll understand what's going on:

  • The key idea here is that manipulating a form element will make something active happen (usually replacement of a piece of the form) without a full page refresh. For example, changing a select might change the options in the rest of the form, without a page refresh.
  • To use AJAX forms, you don't need to know or use or see any javascript. It's all handled for you by the Form API.
  • To AJAX-enable a form element (to make it do something active when it's manipulated) you just set the #ajax property on it
  • When the form element changes (or is clicked, or whatever) a form submission is done by the client in the background.
  • The server-side code rebuilds the form (in the formbuilder function) in a different way
  • The #ajax['callback'] function chooses what part of the form to replace on the page
  • Replacement HTML is returned to the client javascript code, which replaces a part of the page.

OK, so here's all you have to do to AJAXify a form:

  1. In your form, set #ajax on an element, perhaps a select.
  2. Your formbuilder function should have logic to rebuild differently when the select changes. (See the simple formbuilder in ajax_example_autocheckboxes.)
  3. #ajax['callback'] will point to a (typically simple) function which often only selects and returns the changed piece of the form. (See the tremendously simple ajax_example_autocheckboxes_callback.)

That's basically it. Of course you can do lots and lots more than this, but the basic entry point for AJAX forms is mighty easy.

Submit handler is not called when a non-submit is called

One key change from Drupal 6 to be aware of is that when you activate a non-submit AJAX-enabled form element, the submit handlers are not called. In Drupal 6, submit handlers were called, and a bit of spaghetti code was typically done there.

Drupal 7 AJAX Forms Resources

Key D7 AJAX Open Issues

There are still a few of important open issues for D7 AJAX that you may want to look at:

Next time: Drupal 7 multistep form changes.

Subscribe to AJAX