Optional results page step in the form API

greg.harvey - October 21, 2008 - 15:51
Project:Drupal
Version:7.x-dev
Component:forms system
Category:feature request
Priority:normal
Assigned:Unassigned
Status:won't fix
Description

We're writing forms in Drupal which end up calling remote web services (using SOAP Client). We build a form with the form API, validate it in the usual way, on submit we call the web service's method and then we want to display the output. BUT we're at the submit stage of the form API, so we either act now or the data is gone.

At the moment we have three options:

  1. Instead of using the submit function, use a multistep form approach to display the results under the form
  2. Don't call the web service at all in the form API, but instead pass the values on somehow, perhaps as arguments (but there are a lot in some cases!)
  3. Call the web service in the submit function and dump the resulting array in to the session variable for later use

It would be great to have this issue solved by the form API, so have some optional, addtional "results" function which occurs after submit and still has the form collection (including 'storage') available to it, e.g.

<?php
function myform_submit(&$form, &$form_state) {
 
// $results is the result of some web service method call - probably an array
 
$form_state['values']['results'] = $results;
}

// this happens AFTER submit, and is optional
// return value must be mark-up, rendered on the same page as the initial form
function myform_results(&$form, &$form_state) {
 
$output = '<div>'.$form_state['values']['results']['foo'].'</div>';
  return
$output;
}
?>

#1

kingandy - November 13, 2008 - 12:18

I'm in a similar situation right now (though under 5.x), and I agree that this would be a useful addition. As an alternative, perhaps we could have some #attribute that would inform the api that the _submit function will be returning content for display rather than a path for redirection? Realistically you're only going to be doing one or the other.

FWIW, I will note that there's a fourth option:

  1. Perform your processing and output the results using drupal_set_message()

I think that's what I'm going to do for now.

#2

greg.harvey - November 13, 2008 - 12:29

Oh, yes - that might be easier than a new hook and make more sense. I like the idea... something like:

<?php
   
'#results' => TRUE,
?>

Which would tell the form API to expect an HTML return value from _submit.

I might have a go at a patch for that, but first I need to work out why my currently submitted patch keeps coming back from testing as failed. Debug information is scant, at best. =(

#3

kingandy - November 13, 2008 - 15:39

Hmm ... have you tried #redirect? Apparently you can set it to false to stop it redirecting after validation; presumably you can then put something in your form generator that will do your processing (when $_POST['submit'] && !form_get_errors()) instead of - or as well as - returning the form...?

I'll give that a go myself but unfortunately not until tomorrow...

#4

greg.harvey - November 13, 2008 - 16:22

Hmmm, I think that just presents you the blank form again though. Effectively it resets you back to the start of the process for that form? I'm not sure though - if you do try it, let me know! =)

#5

greg.harvey - November 14, 2008 - 11:16

Another idea just came from a chat here - if the FormAPI had a 'markup' field type, then you could use a standard multi-step form and output HTML on the last page with a theme function.

Which is even simpler and involves no real change to the FormAPI!

What do you think? =)

#6

kingandy - November 17, 2008 - 10:33

I could swear I filled out a reply to this last week.

It basically said - yes - the multistep form with a #type=markup element works very well (it basically does the "#redirect=false with $_POST variable processing" trick I outlined above, but more securely and within the FAPI framework).

My form now goes:

<?php
function myform($form_values = null) {
 
  if (!isset(
$form_values)) {
   
$step = 1;
  }
  else {
   
$step = $form_values['step'] + 1;
  }
 
$form = array(
   
'#redirect' => false,
   
'#multistep' => true,
   
'step' => array('#type' => 'hidden', '#value' => $step),
  );
 
  switch (
$step) {
    case
1:
     
// BUILD form here
     
break;
    case
2:
     
$markup = '';
     
// PROCESS results in $markup
     
$form['result'] = array( '#value' => $markup, '#type' => 'markup' );
      break;
  }
  return
$form;
}
?>

If you're not already familiar with multistep forms (as I wasn't), do be aware that when a form is submitted each previous step is processed - I believe it has to build the $form tree in order to validate it - which means if you plan to have further steps beyond your SOAP call you may wish to cache the results in a session variable or such to improve performance.

On a related note, I have this vague notion that it would be better practice to increment the $step in a submit function (since it's called on every page, after validation, but before the next page is built) but have no real evidence to back that up.

#7

kingandy - November 17, 2008 - 13:51

if you plan to have further steps beyond your SOAP call you may wish to cache the results in a session variable or such

Oh, I guess a good way to do it would be something like:

<?php
if ($form_values['soap_result']) {
 
$soapResult = unserialize($form_values['soap_result']);
 
$form['soap_result'] = array('#value' => $form_values['soap_result'], '#type' => 'hidden');
}
else {
 
$client = new SoapClient('http://whatever');
 
$soapResult = $client->SoapMethod($vars);
 
$form['soap_result'] = array('#value' => serialize($soapResult), '#type' => 'hidden');
}
?>

Unless the result values were too big to want to pass back and forth in $_POST values, in which case you'd use the $_SESSION - or if not all the result is relevant, extract the important values and pass them back and forth.

#8

greg.harvey - November 17, 2008 - 13:56

Yes, we're using the session too, because we have to pass out to another page, but if it can live in the form then that's much better.

Btw, I take it your code posted in #6 is hypothetical? Or have you actually been able to implement this approach already?? :-/

#9

kingandy - November 17, 2008 - 14:18

The code in #6 is already working in my 5.x site. #7 is hypothetical but I'm just about to try it out.

I'm not sure if this is directly related to the original feature request any more - in fact it should probably be marked as "won't fix" since the functionality is already achievable using #redirect and #multistep (but I'll leave that to an actual Drupal Project developer). Maybe I should take the specifics of this case to a forum post. Or even a "how-to" if I'm feeling big-headed.

#10

greg.harvey - November 17, 2008 - 15:32

I think this would be a superb HOWTO.

One slight concern is while the 'markup' type might appear to "work" in D5, if it's not a truly defined type it might have consequences we aren't aware of, so there might still be something to do here:

Namely, create the 'markup' type in the API properly, so it's something the Form API expects and knows how to handle...?

#11

kingandy - November 17, 2008 - 15:53

'Markup' is already a defined type - in fact it's the default type if no other #type is provided.

http://api.drupal.org/api/file/developer/topics/forms_api_reference.html...

#12

greg.harvey - November 17, 2008 - 17:05
Status:active» won't fix

LOL... I'm such an idiot! I didn't realise. I was making a suggestion and happened to choose the same name! In that case, I'll "won't fix" this because it's already possible if you RTFM! =)

Thanks for the research. You should definitely write a HOWTO. Let me know if that's ok - if you don't have time, I can write it.

 
 

Drupal is a registered trademark of Dries Buytaert.