I've just gone through the same thing myself, but without using CTools.
While I don't currently have time to write a comprehensive tutorial (which is what's really necessary, as most of the examples out there suck big time) - perhaps on the coming weekend - I can give you an overview of how I've done it. My scenario involves an existing form (for Entity Creation via the Entity Construction Kit).
First and foremost, I'm modifying the form via hook_form_alter()
, but you can do the same thing in any form builder (for, say, a custom form). For simplicity, I'll refer to my implementation of hook_form_alter()
as the 'form builder.'
The first thing my form builder does is determine the current 'state' of the form. I define 'state' as a single, distinct step of the form. I check for current state by looking for $form_state['current_state']
and defaulting it to a default (first step) state if not found.
It is important to understand that the data in $form_state
will persist between steps of the multi-step, so when we trigger the rebuilding of the form, we can see what state we came from and act accordingly. So, we've set the $form_state['current_state']
to a default state (step1
) and added the form elements we wish to display for this state into $form
. (If you have other elements in $form
which you don't want to show, set their #access
property to false
.) Now we can view the form and see our elements - great.
The next step is adding a new state and switching to it upon form submission. When the form is submitted, our submit handler looks at $form_state['current_state']
and figures out how to proceed. We can look at $form_state['triggered_element']['#value']
to figure out which button was clicked to submit the form, and act accordingly. Let's say our button in step1
was 'Next,' we can identify this click in the submit handler and do the following:
- Save the current form values (
$form_state['values']
) into $form_state['step1_values']
.
- Change
$form_state['current_state']
to be step2
.
- Rebuild the form by setting
$form_state['rebuild'] = TRUE;
When the above events happen, the form builder will be called again, and now your $form_state['current_state']
will be set to step2
, so you can identify this and show different form elements. Rinse and repeat for more form steps.
The tricky part I encountered was moving 'back' to a previous state and re-populating the form with previously submitted values. For some reason I had the impression that this would work 'automagically, because Drupal,' but no dice, I had to do it manually. This is no simple task, but (luckily for you!) you can get away with a single line of code:
form_set_defaults($fields, $values, $form);
... where $fields
is an array of machine names for the fields you want to populate, $values
is the data you saved in $form_state['step1_values']
, and $form
is the current form, which is accepted by reference and modified. As for the function itself, it's available as part of the form component in my DevTools module.
Disclaimer: form_set_defaults()
was only tested in my own use case of a textfield, textarea, a series of checkboxes (referencing a taxonomy), and a couple FiveStar widgets. Your mileage may vary.
This should get you most of the way there!