Category Technology
Publication date
29 May 2009

Creating multi-step node forms

Recently I needed a create a multi-step node form in Drupal 6. Unlike other forms in Drupal, it wasn't as simple as configuring a new submit handler that sets $form_state['rebuild'] to TRUE. After trying a few different ways and a bit of searching, I found the solution. The trick is to hide the 'submit' button and use hook_form_alter() on the 'preview' button to regenerate the form for step 2. However, this is probably best explained with some sample code to illustrate.

The first thing you need to do is to define the node form. We're going to use a simple two-step form. On the first page will be the node title and body area, and on the second page a textarea for additional information. Which page of the multistep form to display is determined by $form_state['storage']['step']. As we will see shortly $form_state['storage']['step'] gets set when the first page of the form is submitted.

<?php
/**
 * Implement hook_form().
 */
function multistep_form(&$node, $form_state) {

 

  // Initial step: display title and body fields.
  if (!isset($form_state['storage']['step'])) {
    $form['title'] = array(
      '#title' => t('Title'),
      '#type' => 'textfield',
      '#required' => TRUE,
      '#default_value' => isset($form_state['storage']['title']) ? $form_state['storage']['title'] : $node->title,
    );
    $form['body_field'] = node_body_field($node, t('Body'), 1);
  }
  // Second step: display 
  else {
    $form['additional_info'] = array(
      '#title' => t('Additional information'),
      '#type' => 'textarea',
      '#required' => TRUE,
      '#default_value' => isset($form_state['storage']['additional_info']) ? $form_state['storage']['additional_info'] : $node->additional_info,
    );
  }

  return $form;
}
?>

Using hook_form_alter() we can change the first page of the form and make it into a multistep form. We can identify the first step of the form by $form_state['storage'][step'] and so only call multistep_make_node_multistep()for that step. This prevents us from hiding the submit button on the final page.

<?php
/**
 * Implement hook_form_alter().
 */
function multistep_form_alter(&$form, $form_state, $form_id) {
  if ($form_id == 'multistep_node_form') {
    $node = $form['#node'];

 

    // Page 1, $form_state['storage']['step'] isn't set yet, so display first form.
    if (empty($form_state['storage']['step'])) {
      // Hide everything except title, body and button fields.
      $fields = array('title', 'body');
      multistep_make_node_multistep($form, $fields, 'multistep_step1_form_next_handler');
    }
  }
}
?>

Originally I used a form id specific form alter function but these form alter functions are invoked before the general case form alter functions. This means that other modules, e.g. menu, that modify the node form do so after the unwanted fields are hidden, regardless of the modules relative weights. The only way to hide the fields added by these other modules is to use the general hook_form_alter() function rather than the form id specific one.

The multistep_make_node_multistep() function takes in an array of fields that should not be hidden, along with the name of the submit function to use with the 'preview' button. Any field that doesn't appear in the array, other than hidden fields and a few other special ones, are prevented from being displayed by setting #access to FALSE.

<?php
function multistep_make_node_multistep(&$form, $fields, $submit_handler) {
  // Hide all the elements we don't want.
  foreach (element_children($form) as $child) {
    if ($child != 'buttons' && !in_array($child, $fields) &&
        (empty($form[$child]['#type']) ||
         ($form[$child]['#type'] != 'hidden'
          && $form[$child]['#type'] != 'value'
          && $form[$child]['#type'] != 'token'))) {
      $form[$child]['#access'] = FALSE;
    }
  }

  // Hide the submit button.
  $form['buttons']['submit']['#access'] = FALSE;

  // Change the 'preview' button to 'Next' and set the submit handler.
  $form['buttons']['preview'] = array(
    '#type' => 'submit',
    '#value' => t('Next'),
    '#weight' => 50,
    '#submit' => array($submit_handler),
  );
}
?>

Finally we configure the submit function for the 'Next' button on the first page of the form. When the button is clicked, this function will be called, setting $form_state['storage']['step'] to 1.

<?php
function multistep_step1_form_next_handler($form, &$form_state) {
  $form_state['storage']['step'] = 1;
}
?>

Using this method, and by using multiple submit functions which increment the value of 'step' each time the 'Next' button was clicked, I was able to make a custom node form into a 4 step form!

Credit goes to dww whose post at http://drupal.org/node/382634#comment-1306916 showed me why using the form id specific hook_form_alter() wasn't hiding all form fields. dww also provides a sample module which implements a simple two step node form which you can download and try out.

Profile picture for user Stella Power

Stella Power Managing Director

As well as being the founder and managing director of Annertech, Stella is one of the best known Drupal contributors in the world.