Improve User Experience by Ajaxing Your Drupal Forms

Drupal's Form API has everything that we love about the Drupal framework. It's powerful, flexible, and easily extendable with our custom modules and themes. But lets face it; it's boooorrrrriinnnnggg. Users these days are used to their browsers doing the heavy lifting. Page reloads are becoming fewer and fewer, especially when we are expecting our users to take action on our websites. If we are asking our visitors to take time out of their day to fill out a form on our website, that form should be intuitive, easy to use, and not distracting.

Lucky for us, the Form API has the ability to magically transform our forms into silky smooth ajax enabled interfaces using the #ajax property in the form array. The #ajax property allows us to jump in at any point in form's render array and add some javascript goodness to improve user experience.

Progressive Enhancement

The beautiful thing about utilizing the #ajax property in our Drupal forms is that, if done correctly, it degrades gracefully allowing users with javascript disabled to use our form without issue. "If done correctly" being the operative phrase. The principals of progressive enhancement dictate that the widget must work for everyone before you can begin taking advantage of all of the cool new features available to us as developers. And yes, javascript is still in the "cool new feature" bucket as far as progressive enhancement goes; but that's an argument for a different time :). So this means that the form submission, validation, and messaging must be functional and consistent regardless of javascript being enabled or not. As soon as we throw that #ajax property on the render array, the owness is on us to maintain that consistency.

Email Signup Widget

As an example, lets take a look at the email signup widget from the Listserv module. The form is very basic.  It consists of a text field and a submit button. It also has a validation hook, to make sure the user is entering a valid email, as well as a submit handler that calls a Listserv function to subscribe the users email to a listserv.


/**
 * Listserv Subscribe form.
 */
function listserv_subscribe_form($form, &$form_state) {
  $form['email'] = array(
    '#type' => 'textfield',
    '#title' => t('Email'),
    '#required' => TRUE,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Subscribe'),
  );
  return $form;
}

/**
 * Listserv Subscribe form validate handler.
 */
function listserv_subscribe_form_validate($form, &$form_state) {
  $email = $form_state['values']['email'];
  // Verify that the email address is valid.
  if (!valid_email_address($email)) {
    form_set_error('email', t('You must provide a valid email address.'));
  }
}

/**
 * Listserv Subscribe form submit handler.
 */
function listserv_subscribe_form_submit($form, &$form_state) {
  $email = $form_state['values']['email'];
  listserv_listserv_subscription($email, 'subscribe');
}

On paper, this form does everything we want, but in the wild it isn't all that we had hoped. This is not the type of form that requires its own page.  It's meant to be placed in sidebars and footers throughout the site. If we place this in the footer of our homepage, our users aren't going to be happy when we take their email, then refresh the page and change their scroll position.

Lets image a user that accidently leaves a ".com" off of the end of their address. When the page is refreshed and they are returned to the top of the page, totally removed from the flow of content, only to see a message that says "Please enter a valid email address", what are the chances that they actually scroll back down the page to fulfill that action? I'm no UX expert but I'm guessing it's not good.

AJAX It Up

We can definitely do better. In our theme's template.php file lets add a form alter.


/**
 * Impliments hook_form_FORMID_alter.
 */
function heymp_form_listserv_subscribe_form_alter(&$form, &$form_state, $form_id) {
  $form['submit']['#ajax'] = array(
    'callback' => 'heymp_ajax_listserv_subscribe_callback',
    'wrapper' => 'listserv-subscribe-form',
  );
}

In this form alter, we are tapping into the specific signup form that we want to enhance. We then add the '#ajax' property to the submit button which tells form that when the user clicks the "submit" button, we are going to jump in and handle things with ajax. We define what function is going to handle our logic under 'callback', and we tell the form which DOM element on the page is going to display our results with 'wrapper'.

Now lets add our callback.


/**
 * Callback for heymp_form_listserv_subscribe_form_alter
 */
function heymp_ajax_listserv_subscribe_callback($form, &$form_state) {
  if (form_get_errors()) {
    $form_state['rebuild'] = TRUE;
  	$commands = array();
  	$commands[] = ajax_command_prepend(NULL, theme('status_messages'));  
    return array('#type' => 'ajax', '#commands' => $commands);
  } 
  else {
    $system_message = drupal_get_messages();
    return t('Thank you for your submission!');
  }
}

Validation

In this callback we are doing a few things. For one, we are checking to see if there are any form validation errors. The form submission is still using the validation handler from our listserv_subscribe_form_validate function above. If the submission doesn't pass form validation it will throw an error, allowing us to see it when we call form_get_errors() in our ajax callback. If there is an error, we need to tell the form to rebuild itself by setting $form_state['rebuild'] = TRUE'. This will allow the form to submit more entries.

Error messaging

Next we need to handle printing out the error message. Drupal's Ajax framework has some built in commands for interacting with the DOM without needing to write any javascript at all. By adding operations to the $commands array, we can display our error messaging as well as adding and removing elements as needed. In our case we are going to use the ajax_command_prepend function to print a message. We then return the commands in a render array fashon. See the Ajax framework documentation for a full list of command options.

Override system message

We also need prevent the system message from printing out our success/error messages when the user does eventually reload the page. Since the messages are sitting in a queue, we can easily empty that queue by calling drupal_get_messages(). The theme('status_messages') function takes care of calling it for us in the error if statement so we need to explicitly call it in the success statement.

Success

If there are no errors, we are just going to return a message that lets our user know that they have successfully completed the operation. The message is returned in whatever element we specified our ajax wrapper property above, which was the #listserv-subscribe-form element.

Summary

So there you go! With a minimal amount of code we've drastically improved our user experience using the #ajax property in the Forms API.

In Part 2 we'll take a look at ditching the Form API's commands array and writting our own javascript.