1

I'm fairly new to Drupal but I'm building a site using Drupal 7. Most of the stuff is pretty standard Drupal things, but one of the things my stakeholders want is a tool that does the following (and I'm not sure the best way to implement this using Drupal 7- suggestions appreciated!):

  • A multi-step form (with 1 question per form) with checkboxes and radio buttons only. Data is saved temporarily for users. At the end of the form when users request their recommendatios the site displays a list of resources (from the database) FORMATTED in a specific way (ie. image, text, and link to resource) IN ORDER of relevance BASED ON the selections they made in the form itself. And all of this should be displayed in a list view.

I'm imagining some sort of FORM API + Views interaction but like I said I'm new. Any suggestions from the community?

1

Don't really need the views to make it much complicated. It is all about creating a module. Here is a basic format.

In Drupal, start by creating a simple module. Two files should be created: The .info file and the .module file. For the sake of simplicity call the module multiform.

multiform.info
name = Multi Form
description = Creates a multi-step form.
core = 7.x
package = Custom
version = 7.0

Now that have the .info file, and go create the .module file. The first thing to add is a menu entry to the page in which the form will show on. Implement hook_menu() inside the module like this:

/**
 * Implements hook_menu().
 */
function multiform_menu() {
  $items['multiform'] = array(
    'type' => MENU_CALLBACK,
    'title' => 'Multi Form',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('multiform_primary_form'),
    'access callback' => TRUE,
  );
  return $items;
}

hook_menu() was implemented with a page callback that renders a form. This function will register a menu item for us to use as our form page.

Add the wrapper form. The reason it need a wrapper form is because the other forms will need to be contained. Without it they are just separate forms. The wrapper form will glue them together. A submit handler must also be created.

    /**
     * Form implementation of primary multiform.
     *
     * @see hook_menu()
     */
    function multiform_primary_form($form, &$form_state) {
      // Check to see if anything has been stored.
      if ($form_state['rebuild']) {
        $form_state['input'] = array();
      }
      if (empty($form_state['storage'])) {
        // No step has been set so start with the first.
        $form_state['storage'] = array(
          'step' => 'multiform_first_form',
        );
      }

      // Return the current form
      $function = $form_state['storage']['step'];
      $form = $function($form, $form_state);
      return $form;
    }

    /**
     * Submit handler for the primary multiform.
     *
     * @see multiform_primary_form().
     */
    function multiform_primary_form_submit($form, &$form_state) {
      $values = $form_state['values'];
      if (isset($values['back']) && $values['op'] == $values['back']) {
        // Moving back in form.
        $step = $form_state['storage']['step'];
        // Call current step submit handler if it exists to unset step form data.
        if (function_exists($step . '_submit')) {
          $function = $step . '_submit';
          $function($form, $form_state);
        }
        // Remove the last saved step so we use it next.
        $last_step = array_pop($form_state['storage']['steps']);
        $form_state['storage']['step'] = $last_step;
      }
      else {
        // Record step.
        $step = $form_state['storage']['step'];
        $form_state['storage']['steps'][] = $step;
        // Call step submit handler if it exists.
        if (function_exists($step . '_submit')) {
          $function = $step . '_submit';
          $function($form, $form_state);
        }
      }
      return;  
    }

$form_state['storage'] is used to store which step of the form that is on. Each time this wrapper is called it will call the next step in the sequence of forms. Add first form with a few example fields. Notice that the $form_state['storage']['step'] is storing this function as the default first called.

    /**
     * Menu callback form. First of three.
     */
    function multiform_first_form($form, $form_state) {
      if(!empty($form_state['storage']))
        $values = $form_state['storage'];
      $form['user_info']['username'] = array(
        '#type' => 'textfield',
        '#title' => t('User Name'),
        '#size' => 15,
        '#maxlength' => 15,
        '#default_value' => isset($values['username']) ? $values['username'] : NULL,
        '#description' => t('The user name that will appear to other users. You should not use your real name.'),
        '#required' => TRUE,
      );
      $form['user_info']['first_name'] = array(
        '#type' => 'textfield',
        '#title' => t('First Name'),
        '#size' => 32,
        '#default_value' => isset($values['first_name']) ? $values['first_name'] : NULL,
        '#required' => TRUE,
      );
      $form['user_info']['last_name'] = array(
        '#type' => 'textfield',
        '#title' => t('Last Name'),
        '#size' => 32,
        '#default_value' => isset($values['last_name']) ? $values['last_name'] : NULL,
        '#required' => TRUE,
      );
      $form['user_info']['email'] = array(
        '#type' => 'textfield',
        '#title' => t('E-mail Address'),
        '#size' => 32,
        '#default_value' => isset($values['email']) ? $values['email'] : NULL,
        '#required' => TRUE,
      );
      $form['submit'] = array(
        '#type' => 'submit',
        '#value' => t('Next'),
      );
      return $form;
    }

This is a simple form that asks the user for some basic information. $form_state['storage'] is used to store the values of each form. As the form progesses it will populate the content here if returned. Easy enough. Now going to skip the validation hanlder, but that should be easy to code. The important part is the submit handler where we will be storing the user's data. Below is an example of the first submit handler.

    /**
     * Submit handler for the first form.
     *
     * @see multiform_first_form()
     */
    function multiform_first_form_submit($form, &$form_state) {
      $values = $form_state['values'];

      // Save values to the form storage, add the next step function callback
      $form_state['rebuild'] = TRUE;  // This is very important to have!
      $form_state['storage']['username'] = check_plain($form_state['values']['username']);
      $form_state['storage']['first_name'] = htmlspecialchars($form_state['values']['first_name']);
      $form_state['storage']['last_name'] = htmlspecialchars($form_state['values']['last_name']);
      $form_state['storage']['email'] = check_plain($form_state['values']['email']);
      $form_state['storage']['step'] = 'multiform_second_form';
    }

The first thing to do is make sure `$form_state['rebuild']` is set to true. This will allow the next form to be returned for rendering rather than a reload. Any value that collected should be placed in `$form_state['storage']`. The last part is setting the next step. This information will be sent to the wrapper form and will process the next form. In this case it is `multiform_second_form()`. In this form there will add checkboxes. The first part of the function is to make sure if checkboxes have been set previously that they are reset. Take a look.

/**
 * Form that is called second
 */
function multiform_second_form($form, &$form_state) {
  // Make sure the default values for the checkboxes are set correctly.  
  if (!empty($form_state['storage'])) {
    $values = $form_state['storage'];
    if (!empty($form_state['storage']['food'])) {
      foreach ($form_state['storage']['food'] as $value) {
        if (!empty($value)) {
          $default_values[] = $value;
        }
      }
      if(empty($default_values)) {
        // Storage and experience are set, but no values were selected.
        $default_values = array();
      }
    }
    else {
      // Storage was set, but experience wasn't.
      $default_values = array();
    }
  }
  else {
    // Storage is not set.
    $default_values = array();
  }

  // Set options
  $checkboxes = array(
    1 => 'Pizza',
    2 => 'Hot Wings',
    3 => 'Subs',
    4 => 'Salads',
    5 => 'Garlic Bread',
    6 => 'Pasta',
    7 => 'Cake',
    8 => 'Ice Cream',
  );

  $form['additional'] = array(
    '#type' => 'fieldset',
    '#title' => t('Additional Information'),
    '#collapsible' => FALSE,
  );
  $form['additional']['food'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Food you like'),
    '#options' => $checkboxes,
    '#default_value' => $default_values,
  );
  $form['additional']['company'] = array(
    '#type' => 'textfield',
    '#title' => t('Company'),
    '#size' => 32,
    '#default_value' => isset($values['company']) ? $values['company'] : NULL,
    '#required' => TRUE,
  );
  $form['additional']['message'] = array(
    '#type' => 'textarea',
    '#title' => t('Additional Information'),
    '#description' => t('Please provide information about your job'),
    '#size' => 32,
    '#default_value' => isset($values['message']) ? $values['message'] : NULL,
  );
  $form['back'] = array(
    '#type' => 'submit',
    '#value' => t('Back'),
    '#limit_validation_errors' => array(),
    '#submit' => array('multiform_second_form_submit'),
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Next'),
  );
  return $form;
}

The first part may seem confusing, but just gathering what the user has picked before (in case they went to another portion of the form and came back here). Here doing the same thing as the other form, but now with two submit forms. One will take to the next form and one to the previous form. See how the submit handler handles this:

/**
 * Submit handler for the second step.
 *
 * @see multiform_second_form_start()
 */
function multiform_second_form_submit($form, &$form_state) {
  $values = $form_state['values'];
  $form_state['rebuild'] = TRUE;
  if (isset($values['back']) && $values['op'] == $values['back']) {
    $input = $form_state['input'];
    // The user clicked the back button, save values that were input
    if (isset($input['food'])) {
      $form_state['storage']['food'] = $input['food'];
    }
    if (isset($input['company'])) {   
      $form_state['storage']['company'] = htmlspecialchars($input['company']);
    }
    if (isset($input['message'])) { 
      $form_state['storage']['message'] = htmlspecialchars($input['message']);
    }
    $form_state['storage']['step'] = 'multiform_first_form';
  }
  else {
    $form_state['storage']['food'] = $values['food'];
    $form_state['storage']['company'] = htmlspecialchars($values['company']);
    $form_state['storage']['message'] = htmlspecialchars($values['message']);
    $form_state['storage']['step'] = 'multiform_third_form';
  }
}

The submit handler checks to see what button was pushed. If the back button was pushed it will set the step back to the previous form, if not it will set the step to the next and final form (at least for this form). Make the last form a nice and simple terms of use with a finished button.

/**
 * Form that is called third
 */
function multiform_third_form(&$form, &$form_state) {
  $form['terms'] = array(
    '#type' => 'fieldset',
    '#title' => t('Terms of Use'),
  );
  $form['terms']['terms_text'] = array(
    '#type' => 'textarea',
    '#title' => t('Terms of Use'),
    '#default_value' => t('I agree to the terms of use'),
    '#disabled' => TRUE,
    '#resizable' => FALSE,    
  );
  $form['terms']['terms_checkbox'] = array(
    '#type' => 'checkbox',
    '#title' => t('I agree to the terms of use'),
    '#required' => TRUE,
  );
  $form['back'] = array(
    '#type' => 'submit',
    '#value' => t('Back'),
    '#limit_validation_errors' => array(),
    '#submit' => array('multiform_third_form_submit'),
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );

  $form['#submit'][] = 'multiform_third_form_submit';

  return $form;
}

Simple like the others, this last form just collects some more information. The storage of information, creation of users, etc can be placed in this form's submit handler.

/**
 * Submit handler for the third step.
 *
 * @see multiform_third_form()
 */
function multiform_third_form_submit($form, &$form_state) {
  $values = $form_state['values'];
  $form_state['rebuild'] = TRUE;

  if (isset($values['back']) && $values['op'] == $values['back']) {
    // The user pushed the back button. Clear out the terms checkbox.
    $form_state['storage']['terms_checkbox'] = NULL;
    $form_state['storage']['step'] = 'multiform_second_form';
  }
  else {
    $form_state['storage']['terms_checkbox'] = htmlspecialchars($values['terms_checkbox']);
    $form_state['storage']['step'] = 'multiform_form_complete';
  }
}

Done! Well, maybe. Notice the last part, calling another form. This is just to get the user to a new page and say "

You have completed the form

". You could redirect to a new page or anything else you want. Here is the code for multiform_form_complete().

/**
 * The complete form (Thank you page) for parent form templates.
 */
function multiform_form_complete($form, &$form_state) {
  $form['message'] = array(
    '#markup' =>  t('Thank you for submitting the form!'),
  );  
  return $form;
}

Please have a try with this module. Modify it according to requirements. Let me know if any questions or comments about this process. It is rather long so didn't get much time to explain every detail!

Get more about multi-step form from the Drupal support here. https://www.drupal.org/node/717750

  • This is great! Thank you so much, I really appreciate you taking the time to help me. This makes a lot of sense. I will actually need to be making a database call and comparing the selections provided by users with items in the database in order to give them a matching recommendation...but I have some php knowledge so I can probably figure that out. Thanks again for all your kind help! – Sage Nov 25 '14 at 18:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.