Use AJAX to support multiple item values in custom Drupal 7 forms

RSS
Sam's picture by Sam

One of the situations that comes up when managing content is that a piece of content might need to support more than one value for a property. In Drupal, we can support this by setting a maximum number of values a field can have or we can set the number to unlimited. When the number is unlimited, Drupal’s entity form is a work of beauty in using AJAX to seamlessly support additional values without page reloads or other awkward devices. Now while Drupal entity form handles most tasks when creating/editing content (via Field API), there are times when you want to gather information outside of creating/editing an entity via Form API. And while the Form API does much of the heavy lifting of custom form building, it doesn’t have built-in way of handling instances when you want to accept multiple, unlimited values for field. Fortunately true to its highly extensive nature, Drupal’s AJAX framework comes to the rescue. The AJAX framework still leaves you up to writing a lot of custom functionality, but it takes care of some of the legwork. Even after reading through the documentation and ajax_example.module source code, it still isn’t very clear how to get off the ground quickly using the AJAX framework, so I’m going to attempt to give a quick tutorial for starting to use the AJAX framework.

For the purposes of this tutorial, we are going to be creating a fictional registration form where we can register multiple participants. So image we’ve created a custom form that collects the registrant’s name. It could look like this:

function custom_registration_form($form, &$form_state) {
  $form['name'] = array(
    '#title' => t('Name'),
    '#type' => 'textfield',
  );

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

  return $form;
}

So far so good. We could add additional fields for contact information, but let’s keep things simple. Next, let’s add a button above the submit button to that will display the fields for additional participants.

$form['add_participant'] = array(
  '#type' => 'button',
  '#value' => t('Add A Participant'),
  '#href' => '',
  '#ajax' => array(
    'callback' => 'custom_registration_ajax_add_participant',
    'wrapper' => 'participants',
   ),
 );

When this button is clicked, an ajax call is sent back to the server, the form is processed again, and whatever the callback function (“custom_registration_ajax_add_participant”) returns will be displayed within the HTML element with the it of the wrapper (“participants”). So let’s write the callback function.

function custom_registration_ajax_add_participant($form, $form_state) {
  return $form['participants'];
}

Then above the add another button add the form element that the callback returns.

$form['participants'] = array(
  '#type' => 'container',
  '#tree' => TRUE,
  '#prefix' => '<div id="participants">',
  '#suffix' => '</div>',
);

Before we create the fields each additional participant has, we need to create a way to store how many participants we have. We have none starting out. So at the beginning of custom_registration_form, let’s add this line to store zero or how many participants exist.

$form_state['storage']['participants'] =
  isset($form_state['storage']['participants']) ? $form_state['storage']['participants'] : 0;

The storage item of $form_state lets us store persistent information between form renders. Next at the bottom of custom_registration_form, let’s increment our participant value so the next time the form is rendered we have one participant.

$form_state['storage']['participants']++;

Now we make the magic happen. Below $form['participants'] we create a new fieldset and name field for our additional participant(s).

if ($form_state['storage']['participants']) {
	for ($i = 1; $i <= $form_state['storage']['participants']; $i++) {
	  $form['participants'][$i] = array(
	    '#type' => 'fieldset',
	    '#tree' => TRUE,
	  );

	  $form['participants'][$i]['name'] = array(
	    '#title' => t('Name'),
	    '#type' => 'textfield',
	  );

	}
}

Of course we could collect other fields for each participant. The if statement stops the form from giving any participants until we have re-rendered the form via our ajax call. Here's the complete code.

function custom_registration_form($form, &$form_state) {
  $form_state['storage']['participants'] =
    isset($form_state['storage']['participants']) ? $form_state['storage']['participants'] : 0;

  $form['name'] = array(
    '#title' => t('Name'),
    '#type' => 'textfield',
  );

  $form['participants'] = array(
    '#type' => 'container',
    '#tree' => TRUE,
    '#prefix' => '<div id="participants">',
    '#suffix' => '</div>',
  );
	
  if ($form_state['storage']['participants']) {
    for ($i = 1; $i <= $form_state['storage']['participants']; $i++) {
      $form['participants'][$i] = array(
        '#type' => 'fieldset',
        '#tree' => TRUE,
      );

      $form['participants'][$i]['name'] = array(
        '#title' => t('Name'),
        '#type' => 'textfield',
      );
    }
  }

  $form['add_participant'] = array(
    '#type' => 'button',
    '#value' => t('Add A Participant'),
    '#href' => '',
    '#ajax' => array(
    'callback' => 'custom_registration_ajax_add_participant',
    'wrapper' => 'participants',
   ),
  );

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

  $form_state['storage']['participants']++;

  return $form;
}



function custom_registration_ajax_add_participant($form, $form_state) {
  return $form['participants'];
}

From here we can use the documentation and example modules for inspiration on other ways to configure our form, but this gets us up and running with minimal effort. What do you think? How have you used Drupal’s Form API and AJAX framework?