UPDATE 19 Apr 2007: Updated the code to solve issue with the $_POST array. See
this post
UPDATE 31 Jan 2007: Make sure you also read
this post further down. It describes a solution for some problems you may encounter. The example chunks and snippets have been updated with the corrected code from that post.
I worked out this example in reponse to
this question but thought it more useful to place it in it’s own topic.
With all the new events eForm now has I hoped it would be a trivial matter to split up a form over multiple pages. Well I was so wrong...
It took me most of the afternoon to work out something reasonable.
The trouble is that with the events you have too little control over the flow of eForm. You can alter it’s templates or the field valuesor make it do something extra like storing things in a database but you can’t dynamically alter it’s settings. Once a form is validated it turns out to be more difficult then I thought or like to stop eForm from doing what it is told to do in the snippet call.
What I wanted to do is create a form split over three separate "pages" and only when all three pages are validated send en email (or do something else) with the results. Come what may, this seems not possible using eForm alone. Once the first page is valid eForm will want to send that email. Luckily a recent addition to the document parser class is giving us just the thing we need, $modx->sendForward() which lets us jump out of eFrom early and fetch a new page object. (even if it is in our case the same page),
We’ll get back to that but first here are the steps I followed to split up a form in three sections.
The complete form will have these fields split up like this:
- first form: name & email
- second form: address & town
- third form: gender
(pretty useless I know, but that’s not the point here)
The Form template chunks
For each section I created a separate chunk. (although I’m listing them here in one block they are 3 different chunks)
<!-- formOne chunk -->
<h3>Starting: step 1 of 3</h3>
<p class="error">[+validationmessage+]</p>
<form method="post" action="[~[*id*]~]" id="contact">
<fieldset>
<input type="hidden" name="formid" value="contact" />
<input type="hidden" name="formStep" value="1" />
<p>Name <input name="name" type="text" eform="Your Name::1:" /></p>
<p>Email <input name="email" type="text" eform="Your Email::1:" /></p>
<input type="submit" name="go" value="Next" /></p>
</fieldset>
</form>
<!-- formTwo chunk -->
<h3>Step 2 of 3</h3>
<p class="error">[+validationmessage+]</p>
<form method="post" action="[~[*id*]~]" id="contact">
<input type="hidden" name="formStep" value="2" />
<input type="hidden" name="formid" value="contact" />
<fieldset>
<p>Address <input name="address" type="text" eform="Your Address::1:" /></p>
<p>Town <input name="town" type="text" eform="Your Town::1:" /></p>
<input type="submit" name="go" value="Next" /></p>
</fieldset>
</form>
<!-- formThree chunk (the last one) -->
<h3>Last step</h3>
<p class="error">[+validationmessage+]</p>
<form method="post" action="[~[*id*]~]" id="contact">
<input type="hidden" name="formStep" value="3" />
<input type="hidden" name="formid" value="contact" />
<fieldset>
<p>Gender: <select name="gender" eform="::1::">
<option value="Male">Male</option>
<option value="Female">Female</option>
</select></p>
<input type="submit" name="go" value="Send form" /></p>
</fieldset>
</form>
Note the hidden field ’formStep’which contains a step counter if you will... we’ll get back to that as well.
A report chunk
The report which will be emailed to us (or stored or whatever you fancy) offcourse contains placeholder for all the fields:
<h3>Report for multiple form example</h3>
<table>
<tr><th>Name</th><td>[+name+]<td></tr>
<tr><th>Email</th><td>[+email+]<td></tr>
<tr><th>Address</th><td>[+address+]<td></tr>
<tr><th>Town</th><td>[+town+]<td></tr>
<tr><th>Gender</th><td>[+gender+]<td></tr>
</table>
And some functions
We’re half way there,.. Here’s 2 event functions I’ve placed in a separate snippet which I’ll discuss in a minute.
<?php
if(!function_exists("beforeParseFunction")){
//Display different form depending on session
function beforeParseFunction( &$fields, &$templates ){
global $_lang;
//which form should we load?
// $step is set in beforeMailSentFunction or comes from the hidden field
// we need to do both so that when we step back (eg. the back button of the browser)
// we still get the proper form
if(isset($_SESSION['formStep']))
$step = $_SESSION['formStep'];
else
$step = isset($_POST['formStep'])?$_POST['formStep']:1;
//Kill the session var as otherwise we can't go back to a previous step at all
unset($_SESSION['formStep']);
$tpl = $templates['tpl'];
// Replace form template when needed
switch($step){
case 1: //form not yet posted so use tpl as set in snippet call
return true;
case 2:
$replaceTpl = "multiForm2";
break;
case 3:
$replaceTpl = "multiForm3";
break;
default: //should never get here
return false;
}
if($step>1)
if( $tmp=efLoadTemplate($replaceTpl) ) $tpl=$tmp; else $tpl = $_lang['ef_no_doc'] . " '$replaceTpl'";
//restore template in array
$templates['tpl']=$tpl;
return true;
}
}
if(!function_exists("beforeMailSentFunction")){
//store results in session and advance form
function beforeMailSentFunction(&$fields){
global $modx;
$step = isset($fields['formStep'])?$fields['formStep']:1;
switch($step){
case 1: //save values in session
$_SESSION['name']= $fields['name'];
$_SESSION['email']= $fields['email'];
$_SESSION['formStep'] = 2; //prepare next step
break;
case 2: //save values in session
$_SESSION['address']= $fields['address'];
$_SESSION['town']= $fields['town'];
$_SESSION['formStep'] = 3; //prepare next step
break;
case 3://last form
//from here we go to the report so we need to restore previous values
// into the fields array so they can be used in the report
$fields['name'] = $_SESSION['name'];
$fields['email'] = $_SESSION['email'];
$fields['address'] = $_SESSION['address'];
$fields['town'] = $_SESSION['town'];
return true; //return now and let eForm do it's thing
default: //should never get here
return false;
}
// except for the last step we clear the $_POST array
// and bypass eform and re-open the page with the next form
//unset($_POST); - doesn't work in php 4.3.xx
$_POST = array();
$modx->sendForward($modx->documentIdentifier);
return false; //
}
}
//return from snippet
return "";
?>
And lastly the snippet calls...
The multiFormFunctions snippet sole function is to inlcude the functions listed above and a such it should be cached. the eForm call I’ve split up over several lines but you all know by now that they need to be on one line!
[[multiFormFunctions]]
[!eForm?
&noemail=`true`
&formid=`contact`
&tpl=`formOne`
&eFormOnBeforeFormParse=`beforeParseFunction`
&eformOnBeforeMailSent=`beforeMailSentFunction`
&thankyou=`formReport`
!]
As you can see &tpl is set to the first of our form chunks so straight out of the box this will display ’formOne’.
I’m using the eFormOnBeforeParse event to intercept the form template and replace it with a new one depending on the step counter. This part is straight forward enough.
The eformOnBeforeMailSent event is used to keep track of the step counter and to temporarily store form values. The eformOnBeforeMailSent event is only fired when the current form is found valid. At step 1 and 2 the form values are stored in the session, the step counter is increased and the rest of eForm execution is skipped using $modx->sendForward(). This (in our case) basically ’reloads’ the page object and in effect restarts eForm. As the step counter is now increased it will show us the next form. At step 3 (the last form) the function restores the stored form values from the previous steps and returns to eForm normally after which eForm will finish and finally send us that email.
The tricky bit is to keep track of the step counter. Initially I tried to do this just with a session variable which was increased at every succesfull (validated) post. The trouble with that is when someone uses the back button on their browser. All that would happen is that the latest form section would be ’resfreshed’ and to the user it would appear that there is no way back.... not ideal.
I’m still using a session variable to keep track of the step counter but it is deleted by the eformOnBeforeParse event as soon as it’s copied it’s value and used it to replace the form. That’s where the hidden field comes in (told you I’d get back to it), The hidden field ensures that the step counter is propagated to the next (or previous) post cycle. So if we now press the browser back button there’s no session variable getting in the way and the hidden field helps in remembering where we are.
Now there is still a drawback to this solution. When going back to a previous screen the form will have lost it’s values. I’m sure this can be overcome but I didn’t get around to that. (Since this has been an exercise for your viewing pleasure only, - I don’t need multi-page forms myself - I’m not likely to get to that either for some time) As the values are still in the session variables it can’t be to difficult to add them to the form again.
Lastly, a warning that this only works in the latest MODx 0.9.5 and with eForm 1.4.1! The sendForward method doesn’t exist in previous versions.
Well, enjoy digesting this and please proof me wrong and come up with a more clever way for multi-page forms (and do share it). It certainly gave me a bit of a headache and made me rethink how the next eForm version should be structured, but that is for another day.....