We launched new forums in March 2019—join us there. In a hurry for help with your website? Get Help Now!
    • 30223
    • 1,010 Posts
    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... sad 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.....
      • 31037
      • 358 Posts
      Wow, thanks for that long explanation! I was thinking of using forms this way, but after this explanation I think I’ll use div and js to hide/show parts of the form as needed. This shouldn’t be any problem with eForm I guess?
        • 30223
        • 1,010 Posts
        Quote from: Uncle68 at Nov 03, 2006, 10:56 AM

        Wow, thanks for that long explanation! I was thinking of using forms this way, but after this explanation I think I’ll use div and js to hide/show parts of the form as needed. This shouldn’t be any problem with eForm I guess?

        That depends... as long as the form is not submitted until all required fields are filled in then no, using JS and hidden divs should be no problem. However if someone submits a form while there’s still some required field hidden. eForm will detect it as an invalid value.

        You can hide behind a browser but you can’t hide from eForm! grin
          • 31037
          • 358 Posts
          Quote from: TobyL at Nov 03, 2006, 11:54 AM
          You can hide behind a browser but you can’t hide from eForm! grin
          Hey, that’s so incredably true! laugh Also, eForm loves hiding things from me, latest hour I’ve been trying to see what is in the fields and templates arraws at different times, not as easy as it sounds! But got it worked out, I have now nice colored divs on my testsite showing values from these arrays before showing tha actual content!

          Soon I’ll have a ready and easy solution for creating all form parts dynamically from databas and putting them on multiple form pages. Surely within a year at maximum! laugh
            • 727
            • 502 Posts
            I’ve run into a problem and hopefully you can help. I have a form with two "pages". The first gets a text field called "stationid" and the second gets a text field called "dateofmeasurement".

            When I fill in the station id and click on next, it shows the next "page" where I can enter the Date of Measurement. However at the top of the form there is the following:

            Some errors were detected in your form:
            The following required field(s) are missing: Date of Measurement
            [undefined] » Tampering attempt detected!
            


            I have checked my chunks and eform callback functions and I cannot see what I am missing. Any ideas?

            Andy
              • 30223
              • 1,010 Posts
              This is probably caused by a hidden field in your form who’s value is dynamically set in your page? Javascript perhaps? Hidden fields have an automatic validation. eForm checks the value of hidden fields when it parses the form template. When the POSTED value is not the same eForm assumes someone is trying to tamper with the values. For static values (i.e. values set before the form template is parsed) this works well but with values set in javascript this fails.

              The way around this problem may be to give the hidden field it’s own validation rule. This should override the default behaviour. For instance:

              <input type="hidden" name="whatever" value="" eform="Whatever:integer:1:Value not within valid range:#RANGE 1-100" />
                • 727
                • 502 Posts
                Hi, thanks for replying! Here is the chunk for the second "page". There is only one hidden field, formStep, and no javascript:

                <p class="error">[+validationmessage+]</p>
                <form method="post" action="[~[*id*]~]" id="form">
                <input type="hidden" name="formStep" value="2" />
                <p>Date of Measurement:<br/>
                <input name="dateofmeasurement" type="text" eform="Date of Measurement::1:" /></p>
                <p><input type="submit" name="go" value="Finish" /></p>
                </form>
                


                Any ideas?

                Andy
                  • 30223
                  • 1,010 Posts
                  Yes I do. It is the formStep hidden field... In the first form this will have the value 1 yes? So when that gets posted eForm will parse the second form and compare it against the value from the first form... 1!=2 ergo a validation error and the tampering attempt message...

                  Try the solution I gave above and make the formStep field something like this:
                  <input type="hidden" name="formStep" value="2" eform=":integer:1:Value out of step:#RANGE 1-3" />
                  

                  (assuming you have a 3 step form...

                  Let me know if this helps.

                    • 727
                    • 502 Posts
                    I still have the same problems, so I cut and pasted the example at the start of this thread. I then added the eform field for the hidden value. The result is:

                    Some errors were detected in your form:
                    The following required field(s) are missing: Your Address, Your Town
                    [undefined] » Value out of step
                    


                    It seems to me that even if the [undefined] > Value out of step is fixed, can the missing fields error also be fixed?

                    Andy
                      • 27376
                      • 576 Posts
                      Quote from: Andy at Jan 30, 2007, 02:35 AM

                      It seems to me that even if the [undefined] > Value out of step is fixed, can the missing fields error also be fixed?
                      Once you parse the data from one of your steps you need to clear the $_POST data so that eForm goes in to normal mode instead of validation mode. I struggled with this issue for quite sometime on a related issue.

                      eForm detects the existence of $_POST data related to your form, since it still exists even AFTER you go to step 2, it will try to validate the data against step 2’s input. Clearing the $_POST data will solve the issue.
                      $_POST = array();


                      Alternatively, you can [tt]unset($_POST[’formid’]);[/tt] if you don’t want to completely destroy the $_POST array.