Form wizards, or multi-step forms, are a process by which the user goes through or can use an arbitrary number of different forms to create a single object or perform a single task. Traditionally the multi-step form is difficult in Drupal because there is no easy place to put data in between forms. No longer! The form wizard tool allows a single entry point to easily set up a wizard of multiple forms, provide callbacks to handle data storage and updates between forms and when forms are completed. This tool pairs well with the object cache tool for storage.
$form_info = array( 'id' => 'delegator_page', 'path' => "admin/structure/pages/edit/$page_name/%step", 'show trail' => TRUE, 'show back' => TRUE, 'show return' => FALSE, 'next callback' => 'delegator_page_add_subtask_next', 'finish callback' => 'delegator_page_add_subtask_finish', 'return callback' => 'delegator_page_add_subtask_finish', 'cancel callback' => 'delegator_page_add_subtask_cancel', 'order' => array( 'basic' => t('Basic settings'), 'argument' => t('Argument settings'), 'access' => t('Access control'), 'menu' => t('Menu settings'), 'multiple' => t('Task handlers'), ), 'forms' => array( 'basic' => array( 'form id' => 'delegator_page_form_basic' ), 'access' => array( 'form id' => 'delegator_page_form_access' ), 'menu' => array( 'form id' => 'delegator_page_form_menu' ), 'argument' => array( 'form id' => 'delegator_page_form_argument' ), 'multiple' => array( 'form id' => 'delegator_page_argument_form_multiple' ), ), );The above array starts with an id which is used to identify the wizard in various places and a path which is needed to redirect to the next step between forms. It then creates some settings which control which pieces are displayed. In this case, it displays a form trail and a 'back' button, but not the 'return' button. Then there are the wizard callbacks which allow the wizard to act appropriately when forms are submitted. Finally it contains a list of forms and their order so that it knows which forms to use and what order to use them by default. Note that the keys in the order and list of forms match; that key is called the step and is used to identify each individual step of the wizard. Here is a full list of every item that can be in the form info array:
$form_state = array(); ctools_include('wizard'); $output = ctools_wizard_multistep_form($form_info, $step, $form_state);If using AJAX or the modal, This part is actually done! If not, you have one more small step:
return $output;
/** * Get the cached changes to a given task handler. */ function delegator_page_get_page_cache($name) { ctools_include('object-cache'); $cache = ctools_object_cache_get('delegator_page', $name); if (!$cache) { $cache = delegator_page_load($name); $cache->locked = ctools_object_cache_test('delegator_page', $name); } return $cache; } /** * Store changes to a task handler in the object cache. */ function delegator_page_set_page_cache($name, $page) { ctools_include('object-cache'); $cache = ctools_object_cache_set('delegator_page', $name, $page); } /** * Remove an item from the object cache. */ function delegator_page_clear_page_cache($name) { ctools_include('object-cache'); ctools_object_cache_clear('delegator_page', $name); }Using these wrappers, when performing a get_cache operation, it defaults to loading the real object. It then checks to see if another user has this object cached using the ctools_object_cache_test() function, which automatically sets a lock (which can be used to prevent writes later on). With this set up, the _next, _finish and _cancel callbacks are quite simple:
/** * Callback generated when the add page process is finished. */ function delegator_page_add_subtask_finish(&$form_state) { $page = &$form_state['page']; // Create a real object from the cache delegator_page_save($page); // Clear the cache delegator_page_clear_page_cache($form_state['cache name']); } /** * Callback generated when the 'next' button is clicked. * * All we do here is store the cache. */ function delegator_page_add_subtask_next(&$form_state) { // Update the cache with changes. delegator_page_set_page_cache($form_state['page']); } /** * Callback generated when the 'cancel' button is clicked. * * All we do here is clear the cache. */ function delegator_page_add_subtask_cancel(&$form_state) { // Update the cache with changes. delegator_page_clear_page_cache($form_state['cache name']); }All that's needed to tie this together is to understand how the changes made it into the cache in the first place. This happened in the various form _submit handlers, which made changes to $form_state['page'] based upon the values set in the form:
/** * Store the values from the basic settings form. */ function delegator_page_form_basic_submit(&$form, &$form_state) { if (!isset($form_state['page']->pid) && empty($form_state['page']->import)) { $form_state['page']->name = $form_state['values']['name']; } $form_state['page']->admin_title = $form_state['values']['admin_title']; $form_state['page']->path = $form_state['values']['path']; }No database operations were made during this _submit, and that's a very important distinction about this system.
/** * Form builder function for wizard. */ function wizardid_step2_form(&$form, &$form_state) { $form_state['my data'] = my_module_get_cache($form_state['cache name']); $form['example'] = array( '#type' => 'radios', '#title' => t('Title'), '#default_value' => $form_state['my data']->example ? $form_state['my data']->example : default, '#options' => array( 'default' => t('Default'), 'setting1' => t('Setting1'), ), ); } /** * Submit handler to prepare needed values for storing in cache. */ function wizardid_step2_form_submit($form, &$form_state) { $form_state['my data']->example = $form_state['values']['example']; }The data is stored in the my data object on submitting. If the user goes back to this step the cached my data is used as the default form value. The function my_module_get_cache() is like the cache functions explained above.
/** * Validation handler for step2 form */ function wizardid_step2_form_validate(&$form, &$form_state) { // if the clicked button is anything but the normal flow if ($form_state['clicked_button']['#next'] != $form_state['next']) { drupal_get_messages('error'); form_set_error(NULL, '', TRUE); return; } // you form validation goes here // ... }
/** * Implementation of hook init */ function mymodule_init() { // if the path leads to the wizard if (drupal_match_path($_GET['q'], 'path/to/your/wizard/*')) { // set cache to false $GLOBALS['conf']['cache'] = FALSE; } }