AJAX forms in Drupal 7

So you’ve finally reached the point in Drupal 7 development where it’s time to use Drupal’s AJAX framework.

You start googling…

Twenty tabs, several hours, and many bad examples later you no longer like the idea of using the built in AJAX framework, or even Drupal.

I know how you feel.

Below I explain how to create your own Drupal 7 module that provides an AJAX form. The form will also work with javascript turned off. Once you get through this post, you should have a better understanding of how Drupal does AJAX.

Setting up the module

Let’s assume your form will live in a shiny new module called custom_newsletter. We’ll start by implementing hook_menu(). This will give us a page that renders our AJAX form.

/**
 * Implements hook_menu().
 */
function custom_newsletter_menu() {
  $items = array();
  $items['custom-newsletter'] = array(
    'title' => 'Custom Newsletter Form',
    'description' => 'Sign up for our newsletter',
    'page callback' => 'drupal_get_form',
    // custom_newsletter_form is the name of the function that will be passed to
    // drupal_get_form. This function is what provides the form.
    'page arguments' => array('custom_newsletter_form'),
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

We now have a new menu item defined: custom-newsletter. When this module is enabled, you’ll be able to visit /custom-newsletter.

The page argument for our menu item is custom_newsletter_form, which is the name that must be used for the function providing the form.

Creating the form

Below is the function that will create the form:

/**
 * Our custom newsletter form.
 */
function custom_newsletter_form($form, &$form_state) {
  // This is used for the #ajax['wrapper']. The underscores need to be replaced
  // with hyphens or else you'll end up with a bad selector for your form.
  $form_id = str_replace('_', '-', $form_state['build_info']['form_id']);

  // Because classes are good for styling
  $form['#attributes'] = array('class' => array('newsletter-form'));

  $form['title'] = array(
    '#markup' => "Sign up for our newsletter!",
    '#prefix' => '<h2 class="newsletter-form-title">',
    '#suffix' => '</h2>'
  );

  $form['description'] = array(
    '#markup' => 'Get our latest content sent right to your inbox every week.',
    '#prefix' => '<div class="newsletter-form-description">',
    '#suffix' => '</div>'
  );

  $form['email'] = array(
    '#type' => 'textfield',
    '#required' => TRUE,
    '#attributes' => array(
      'class' => array('newsletter-form-email'),
    ),
  );

  $form['actions']['#type'] = 'actions';
  $form['actions']['submit'] = array(
    '#type' => 'submit',
    '#weight' => 10,
    '#value' => 'Sign up',
    '#ajax' => array(
      'callback' => 'custom_newsletter_form_ajax',
      'event' => 'click',
      'method' => 'replace',
      'wrapper' => $form_id,
    ),
  );

  return $form;
}

Let’s review what this form will render:

  • A title and description to provide information to our visitors prior to submitting the form.
  • A text field for collecting an email address
  • A submit button

The submit button is what we will use to interact with Drupal’s AJAX framework.

As you can see in custom_newsletter_form(), the submit button contains the #ajax property, which contains four elements: callback, event, wrapper, and method.

  • The callback is the function that will be called when the form is submitted. In this case, it is custom_newsletter_form_ajax.
  • The event is the jQuery event that will trigger the callback. We could probably use mousedown too, but click works fine.
  • With method set to replace, any HTML returned by our callback will replace the contents of the wrapper element i.e. form#FORM_ID. Note that replace is the default value, so we technically do not need it defined here for this example. I personally define it anyway.
  • wrapper is set to the ID of the form. This will let us append messages inside of form#FORM_ID and replace form#FORM_ID.

There’s only three more functions left in order for this form to be awesome:

  • The AJAX callback: custom_newsletter_form_ajax
  • The validation handler: custom_newsletter_form_validate
  • The submission handler: custom_newsletter_form_submit

The AJAX callback

/**
 * The ajax callback for custom_newsletter_form.
 */
function custom_newsletter_form_ajax(&$form, &$form_state) {
  if (form_get_errors()) {
    return $form;
  }

  $commands = array();
  $commands[] = ajax_command_replace(NULL, theme('status_messages'));

  return array(
    '#type' => 'ajax',
    '#commands' => $commands,
  );
}

Here’s a brief overview of custom_newsletter_form_ajax: If the form is invalid, display an error message to the user, but if the form is valid it will be submitted and the form will be replaced with a friendly message.

Note that when form_get_errors() is true, we’re just returning the form. As far as I can tell, there’s no need to set $form_state[‘rebuild’] to TRUE before returning $form because ajax_form_callback() already takes care of processing the form before invoking our callback.

When the form passes validation, any status_messages we set with drupal_set_message() during the submission will be returned.

Validating the form

Almost done. The validation handler is next.

/**
 * The validate callback for custom_newsletter_form.
 */
function custom_newsletter_form_validate($form, &$form_state) {
  if (!valid_email_address($form_state['values']['email'])) {
    form_set_error('custom_newsletter', t('Please enter a valid email address.'));
  }
}

valid_email_address() is a Drupal wrapper around PHPs filter_var(), which is a function that can do some basic filtering on an email address. When an invalid email is entered, the error message will be set using form_set_error() and displayed by our fancy ajax callback checking for errors and returning a rebuilt form.

Submitting the form

And finally, when the email address is valid, we will end up in the submit handler:

/**
 * The submit callback for custom_newsletter_form.
 */
function custom_newsletter_form_submit($form, &$form_state) {
  // Grab the email address for processing later
  $email = $form_state['values']['email'];

  //
  // Do email processing here
  //
  // References:
  // - https://api.drupal.org/api/drupal/includes!mail.inc/function/drupal_mail/7
  // - https://api.drupal.org/api/examples/email_example%21email_example.module/7
  //

  // Let's assume we successfully processed the submission.
  $success = TRUE;

  if ($success) {
    drupal_set_message(t('Your submission was successful. An email will be sent to @email.', array('@email' => $email)), 'status');
  }
  else {
    drupal_set_message(t('There was a problem submitting the form.'), 'warning');
  }
}

You’ll need to add some logic to the submit handle to actually process the email address. This example will always return an successful submission. To see what happens when the form submission fails, just set $success to FALSE.

That’s it. Not so bad, huh?

Note that custom_newsletter_form_validate and custom_newsletter_form_submit are called automatically. Drupal treats functions beginning with the name of the form (i.e. custom_newsletter_form) and ending with _submit and _validate as the default form handlers. Enable the custom_newsletter module on your Drupal 7 site, visit /custom-newsletter, and witness Drupal’s AJAX framework in action. You no longer need to pull your hair out over Drupal AJAX forms.