Recently I've been struggling with creating a multi-step CCK node form in Drupal 5. I was able to implement a solution that used a combination of form_alter and jQuery, but due to the amount of jQuery, it was too slow. I then attempted the same functionality in Drupal 6, but due to the number of changes in the Forms API, I decided to take a step back. Here's my first successful attempt at creating a multi-step form using a Drupal 6 module and the Forms API. The following code adds the ability to jump to any step of the form, save at any point, and dynamically creates steps based on the number of fieldsets. My next step will be transposing this code to work with a CCK node form.
Screenshot:

<?php
function multipage_menu() {
$items = array();
$items['multiStepForm'] = array(
'title' => t('Multi Step Form'),
'page callback' => 'drupal_get_form',
'page arguments' => array('multipage_test_form'),
'access arguments' => array('access content'),
);
return $items;
}
function multipage_test_form(&$form_state) {
// enable/disable debugging output
$form_state['storage']['#navigation']['debugging'] = false;
// per debugging
if ($form_state['storage']['#navigation']['debugging']) echo "<HR>LOADING FUNCTION: multipage_test_form<HR>";
// ensure step is set in storage
if (empty($form_state['storage']['#navigation']['step'])) {
$form_state['storage']['#navigation']['step'] = 1;
} elseif ($form_state['storage']['#navigation']['newStep']) {
// if the form has been submitted, check for a new step
$form_state['storage']['#navigation']['step'] = $form_state['storage']['#navigation']['newStep'];
unset ($form_state['storage']['#navigation']['newStep']);
}
$form = array();
// add form fields
_multipage_test_form_add_fields($form, $form_state);
// calculate max number of steps
_multipage_test_form_max_steps($form, $form_state);
// create tabs
_multipage_test_form_create_tabs($form, $form_state);
// remove form fields per step
_multipage_test_form_remove_fields($form, $form_state);
// add tabs to form
_multipage_test_form_add_tabs($form, $form_state);
// add buttons to form
_multipage_test_form_add_buttons($form, $form_state);
return $form;
}
function _multipage_test_form_add_fields(&$form, &$form_state) {
// per debugging
if ($form_state['storage']['#navigation']['debugging']) echo "<hr> ADDING FIELDS<hr>";
// Fieldset A
$form['fieldset_a'] = array(
'#type' => 'fieldset',
'#title' => t('Fieldset A'),
);
$form['fieldset_a']['textfield_a1'] = array(
'#type' => 'textfield',
'#title' => t('Textfield A1'),
'#default_value' => $form_state['storage']['textfield_a1'],
);
$form['fieldset_a']['textfield_a2'] = array(
'#type' => 'textfield',
'#title' => t('Textfield A2'),
'#default_value' => $form_state['storage']['textfield_a2'],
);
$form['fieldset_a']['textfield_a3'] = array(
'#type' => 'textfield',
'#title' => t('Textfield A3'),
'#default_value' => $form_state['storage']['textfield_a3'],
);
// Fieldset B
$form['fieldset_b'] = array(
'#type' => 'fieldset',
'#title' => t('Fieldset B'),
);
$form['fieldset_b']['textfield_b1'] = array(
'#type' => 'textfield',
'#title' => t('Textfield B1'),
'#default_value' => $form_state['storage']['textfield_b1'],
);
$form['fieldset_b']['textfield_b2'] = array(
'#type' => 'textfield',
'#title' => t('Textfield B2'),
'#default_value' => $form_state['storage']['textfield_b2'],
);
$form['fieldset_b']['textfield_b3'] = array(
'#type' => 'textfield',
'#title' => t('Textfield B3'),
'#default_value' => $form_state['storage']['textfield_b3'],
);
// Fieldset C
$form['fieldset_c'] = array(
'#type' => 'fieldset',
'#title' => t('Fieldset C'),
);
$form['fieldset_c']['textfield_c1'] = array(
'#type' => 'textfield',
'#title' => t('Textfield C1'),
'#default_value' => $form_state['storage']['textfield_c1'],
);
$form['fieldset_c']['textfield_c2'] = array(
'#type' => 'textfield',
'#title' => t('Textfield C2'),
'#default_value' => $form_state['storage']['textfield_c2'],
);
$form['fieldset_c']['textfield_c3'] = array(
'#type' => 'textfield',
'#title' => t('Textfield C3'),
'#default_value' => $form_state['storage']['textfield_c3'],
);
// Fields not in a fieldset
$form['textfield_d1'] = array(
'#type' => 'textfield',
'#title' => t('Textfield D1'),
'#default_value' => $form_state['storage']['textfield_d1'],
);
$form['textfield_d2'] = array(
'#type' => 'textfield',
'#title' => t('Textfield D2'),
'#default_value' => $form_state['storage']['textfield_d2'],
);
$form['textfield_d3'] = array(
'#type' => 'textfield',
'#title' => t('Textfield D3'),
'#default_value' => $form_state['storage']['textfield_d3'],
);
}
function _multipage_test_form_max_steps($form, &$form_state) {
// calculate max stpes if empty
if (empty($form_state['storage']['#navigation']['maxSteps'])) {
$fieldsetCount = 0;
$otherFields = false;
// loop through form elements
foreach ($form as $k => $v) {
// check for fieldset
if (substr($k,0,9)=='fieldset_' && is_array($v) && $v['#type']=='fieldset') {
$fieldsetCount++;
} else {
$otherFields = true;
}
}
// if there are other fields, increment max steps
if ($otherFields) {
$fieldsetCount++;
$form_state['storage']['#navigation']['otherFields'] = true;
}
// add count to storage
$form_state['storage']['#navigation']['maxSteps'] = $fieldsetCount;
}
}
function _multipage_test_form_create_tabs(&$form, &$form_state) {
// per debugging
if ($form_state['storage']['#navigation']['debugging']) echo "<hr> CREATING TABS<hr>";
// create list of tabs if not set in storage
if (empty($form_state['storage']['#navigation']['tabs'])) {
$tabCount = 0;
$tabs = array();
// check for other fields
if ($form_state['storage']['#navigation']['otherFields']) {
$tabsCount++;
$tabs[$tabsCount] = "Start";
}
// loop through form items
foreach ($form as $k => $v) {
if (substr($k,0,9)=='fieldset_' && is_array($v) && $v['#type']=='fieldset') {
$tabsCount++;
$tabs[$tabsCount] = $v['#title'];
}
}
// add tabs to storage
$form_state['storage']['#navigation']['tabs'] = $tabs;
}
}
function _multipage_test_form_remove_fields(&$form, &$form_state) {
// per debugging
if ($form_state['storage']['#navigation']['debugging']) echo "<HR> REMOVING FIELDS<HR>";
// set fieldset counter based on otherfields
if ($form_state['storage']['#navigation']['otherFields']) {
$fieldsetCount = 1;
} else {
$fieldsetCount = 0;
}
// loop though form fields
foreach ($form as $k => $v) {
// ensure this form item is a fieldset
if (substr($k,0,9)=='fieldset_' && is_array($v) && $v['#type']=='fieldset') {
// increment count of fieldsets
$fieldsetCount++;
// unset fieldset
if ($form_state['storage']['#navigation']['step'] != $fieldsetCount) unset($form[$k]);
} elseif (is_array($v) && $form_state['storage']['#navigation']['otherFields'] && $form_state['storage']['#navigation']['step']>1) {
// unset field
unset($form[$k]);
}
}
}
function _multipage_test_form_add_tabs(&$form, $form_state) {
// per debugging
if ($form_state['storage']['#navigation']['debugging']) echo "<HR> ADDING TABS<HR>";
$tabsArray = array();
$tabsCount = 0;
// loop through tabs and create buttons for each one
foreach ($form_state['storage']['#navigation']['tabs'] as $k => $v) {
$tabsCount++;
$tabsArray['tab_'.$k] = array(
'#type' => 'submit',
'#value' => t($v),
'#attributes' => array(
'class' => 'tabs' . ($tabsCount == $form_state['storage']['#navigation']['step'] ? ' active' : '')
),
);
}
// add tab buttons to beginning of form
$form = array_merge($tabsArray, $form);
}
function _multipage_test_form_add_buttons(&$form, &$form_state) {
// per debugging
if ($form_state['storage']['#navigation']['debugging']) echo "<HR> ADDING BUTTONS<HR>";
// add previous button
if ($form_state['storage']['#navigation']['step']>1) {
$form['previous'] = array(
'#type' => 'submit',
'#value' => t('Previous'),
);
}
// add next button
if ($form_state['storage']['#navigation']['step']<$form_state['storage']['#navigation']['maxSteps']) {
$form['next'] = array(
'#type' => 'submit',
'#value' => t('Next'),
);
}
// add save button
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
);
}
function multipage_test_form_validate($form, &$form_state) {
// per debugging
if ($form_state['storage']['#navigation']['debugging']) echo "<hr>FORM VALIDATED<hr>";
}
function multipage_test_form_submit($form, &$form_state) {
// per debugging
if ($form_state['storage']['#navigation']['debugging']) echo "<hr>FORM SUBMITTED<hr>";
// loop through submitted form values
foreach ($form_state['values'] as $k => $v) {
// add certain fields to storage
if (substr($k, 0, 10)=='textfield_') {
$form_state['storage'][$k] = $v;
}
}
// handle button action
switch ($form_state['clicked_button']['#value']) {
case 'Previous':
// set the state to rebuild
$form_state['rebuild'] = TRUE;
// set new step
if ($form_state['storage']['#navigation']['step']>1) {
$form_state['storage']['#navigation']['newStep'] = $form_state['storage']['#navigation']['step']-1;
}
break;
case 'Next':
// set the state to rebuild
$form_state['rebuild'] = TRUE;
// set new step
if ($form_state['storage']['#navigation']['step']<$form_state['storage']['#navigation']['maxSteps']) {
$form_state['storage']['#navigation']['newStep'] = $form_state['storage']['#navigation']['step']+1;
}
break;
case 'Save':
// TODO
break;
// tabs
default:
// check if the button value maps to a tab
$key = array_search($form_state['clicked_button']['#value'], $form_state['storage']['#navigation']['tabs']);
if ($key) {
$form_state['storage']['#navigation']['newStep'] = $key;
}
break;
}
}
?>










Insert elements into a fieldset on your multi step form
I'm struggling with adding some code to your example form. I've tried several different ways but none have been successful.
I want to have a button at the bottom of one of my fieldsets that will add form elements to only this fieldset.
I was using code from the following link.
http://jamestombs.co.uk/2010-07-08/adding-additional-form-elements-using...
Thanks!
I am a newbie
I am a total newbie, please can you guide me that how can i Use this code snippet in Drupal ?
module
This code would have to be inserted into a custom module. I would start here: http://drupal.org/developing/modules
A modules in its most simple form consists of a directory, .info, and .module file.
Applying this to a node form
Hi. I'm starting to try to apply this to a node form, which is easier said than done. My thinking is that the first thing one would need to do is build the form in hook_form_alter instead of in the multipage_test_form function. The idea being that cck fields are added in a hook_form_alter (I think), so the navigation wouldn't see them otherwise. When I make this change, it seems to not cache after the second rebuild however. Surprisingly, if I take this code and make an absolute minimal version with two fieldsets and put that in hook_form_alter, it runs just fine. I'm not sure what the difference is yet.
Ideas or suggestions? Have you made any progress on your original goal to make a multi-step CCK node form in Drupal 6? Cheers
Is it possilble to go to the next part of the form onselect?
I have set up a select list for fieldset A. Is it possible to go to fieldset B onselect instead of having to click next? thanks Nate
Thank you eric.can view be
Thank you eric.can view be used with this?please show how.
Hi Eric If I want to use this
Hi Eric
If I want to use this for creating a new/custom content type how will I do this wican I make it take the $node as wel as $form_state variable .please help
Hi Eric. This is awesome.
Hi Eric. This is awesome. Thank you very much for this. I wanted to use this for a content management part(create custom content).can the data be saved to a database directly at each step rather than the ds store? If so please give me an example.thank you in advance.
etse
works great
thanks a ton for this - it is working perfect.
Next question - is there anyway to get into this form at the end? Via a $_GET maybe? I want to point some users to the max_step page (is last) rather that the first page.
thanks
I think so...
At the top of the code, if the step does not exist, you could set it to the last step instead of the 1st...
<?phpif (empty($form_state['storage']['#navigation']['step'])) {
$form_state['storage']['#navigation']['step'] = 'SET THE LAST STEP HERE';
}
?>
Was looking for this
Was happy to come across this. I've been looking for help on how to go back and forth across pages. This works just grand.
One question - How would I place all the $form_state['storage'] into a final fieldset such that the last button on the tabs at the top, called, for example fieldset_e, title=>'Results', would show the cumulative results of all their "saves". I can display it via a print_r of course, but I'd like them to be in the final fieldsets_e.
thanks for this.
one idea
You could check for the current page and then loop through the $form_state['storage'] variable and generate the html...
<?php
if ($form_state['storage']['#navigation']['step'] == $form_state['storage']['#navigation']['maxSteps'])
{
$html = "";
// loop through storage
foreach ($form_state['storage'] as $k => $v)
{
$html .= $k . " = " . $v . "<BR>";
}
return $html;
}
?>
Please note, I did not test the above code :)
Thanks
Thanks for the quick reply. Will give it a go.
K
where to copy this code?
Hi,
Please tell me where to copy this code?