Thursday 27 January 2011

Next and previous

The decision about which page of the multi-page form to go to next is given to the calling code.

You can set up a 'next callback' in $form_info, but not a 'previous callback'. The 'next callback' is called in both cases. The default function name is FORM_ID_next(&$form_state).

In here you control where to go next. The simplest solution is either you want to go to the next, or to the previous. Like this:

<?php
function mywizard_next(&$form_state) {
if (isset($form_state['triggering_element']['#next'])) {
$form_state['my_storage']['step'] = $form_state['triggering_element']['#next'];
}

// set the values built by this page
$form_state['my_storage'][$form_state['step']] = $form_state['values'];

// Update the cache with any changes.
mywizard_cache_set('form_values', $form_state['my_storage']);
}
?>

Why do it like this? Because, being one for elegance and aesthetics, I do not want the current step to appear in the URL (mywizard/step1). I want just the one URL (mywizard). Also notice the use of "triggering_element" which is an addition for Drupal 7, and is identical to "clicked_button". However "clicked_button" will be discontinued in Drupal 8.

The Chaos Tools form wizard needs you to build the $form_info array and call its function 'ctools_wizard_multistep_form' each time the page is to be rebuilt. So, in my function that gets called for the page I do some interesting things.

<?php
function mywizard_wizard() {
ctools_include('wizard');

// Fetch the form info array
$form_info = mywizard_form_info();

// Fetch the current form values from the cache...
$form_values = mywizard_cache_get('form_values');
if (empty($form_values)) {
// ...but if they aren't there, build the initial values
$form_values = array(
// set the first step
'step' => array_shift(array_keys($form_info['order'])),
// and my default start values (if any)
'mywizard_values' => array(),
);
mywizard_cache_set('form_values', $form_values);
}

// Build $form_state array
$form_state = array(
'my_storage' => $form_values,
);

// And build the current step
return ctools_wizard_multistep_form($form_info, $form_values['step'], $form_state);
}
?>
So, if you can follow this, when we enter the function we build the $form_info array which describes the whole multi-page form. If this was a complicated process (it could be) you could also cache the $form_info array so it only needs to be done once.

Then we either fetch the cached form data, which contains the next step, or build a new one if we don't have one (in other words this is the first time through).

We save this information in $form_state and then call the ctools multi-step form builder which finds the right form and builds it.

When "next" is clicked (or "back") we save the current step and cache it. To be picked up when the page is built next time.

Voila.

Edited later to remove some unnecessarily complex code. This should be viewed in conjunction with the Chaos Tools Advanced Help.

No back validate

Here's an undocumented option for your Chaos Tools form wizard (not sure if it's in the D6 code): 'no back validate'.

It prevents validation of the current form when you click the back button which means you can use #required fields and prevent Drupal from insisting the user fills them in before going back through the form.

You can either put it in the root level of the $form_info array, or in one or more of the form description entries. The former means that the back button will always prevent validation, with the latter you can control which forms do and don't have validation on the back button.

Handy.

No form output with Chaos Tools multi-step form wizard

Hey this is my thrilling new Drupal 7 blog - it's for hints and tips and Drupal philosophy which I'll add as I go along.

So here's something on the Chaos Tools multi-step form implementation which might easily catch you out and leave you swearing at the screen for an hour or two (like it did me):

So, are you wondering why your D7 multi-step form doesn't produce any output at all?

The form step builders must "return $form;" instead of having the form passed by reference:

<?php
function mymodule_step_form($form, &$form_state) {
// add my fields
return $form;
}
?>

Hope that saves others some time.

I also posted this in the Chaos Tools issue queue here.