Form Validation and Flow -- One Technique

Form Validation and Flow -- One Technique

There are multiple ways and techniques to program almost any task. Because of this, there is one thing I can guarantee: If you analyze how three different applications validate form data, you will find three different ways of doing it (assuming they validate at all). Some techniques are better than others. Here I'll describe one that works for me.

A little about me first so you understand where I am coming from. I have been a professional application designer and programmer for over 20 years; the last ten years I have had a heavy focus on web based applications using various technologies. I'll be the first to admit that my views of user applications is myopic -- I tend to see everything with a "how usable is the application to the user" and "how is the user going to break the application."

Now two of my pet peeves: 1) Programs that don't gracefully display entry errors to the users and 2) Programs that don't validate EVERYTHING that the user enters. To me, customer impression wise, there is nothing worse that a generic "Invalid data, press the back button and try again." System security wise, there is nothing worse that assuming that FORM data is valid and blindly acting on it. Also, client side validation (JavaScript, etc.) is good for "customer impression" but cannot be used as a substitute for server side validation.

Anyway, now that bored stiff with my high and mighty ideals, I'll present you with a simple technique I use for validating data.

The biggest difference between this technique and most "common" tutorials that I have seen on CF is that the FORM page and the ACTION page are one and the same and this has three key benefits that can be problematical using separate FORM and ACTION pages:

First, you have full control on what information is "defaulted" to the user in form fields and you can preserve any previously keyed information in the event of any entry errors. Third, with some very minor tweaks, you can allow the passing of "default" form field values via URL parameters. Third, using the <cflocation> tag to direct the user to the next page allows for refresh to work properly without the nagging "do you want to repost" browser message and all the associated code to handle double-posts.

MySampleForm.cfm:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

<cfparam name="FORM.FirstName" default="">
<cfparam name=
"FORM.LastName" default="">
<cfparam name=
"FORM.CardNumber" default="">
<cfparam name=
"FORM.ExpirationMonth" default="">
<cfparam name=
"FORM.ExpirationYear" default="">
<cfparam name=
"FORM.Action" default="">

<cfset VARIABLES.y1=DatePart(
"yyyy",Now())>
<cfset VARIABLES.y2=VARIABLES.y1+10>

<cfparam name=
"URL.Msg" default="">
<cfset VARIABLES.Msg=
"#URL.Msg#">

<cfif VARIABLES.Msg is "" and FORM.Action is "Submit">
  <!-- validate data -->
  <cfif Trim(FORM.FirstName) is
"">
    <cfset VARIABLES.Msg=VARIABLES.Msg &
"<li>You must enter your first name</li>">
  </cfif>
  <cfif Trim(FORM.LastName) is
"">
    <cfset VARIABLES.Msg=VARIABLES.Msg &
"<li>You must enter your last name</li>">
  </cfif>
  <cfif Trim(FORM.CardNumber) is
"">
   
<!--
     This is a simple "card number entered" check for the
     purpose of this example; I would do a more thorough
     check to determine number was valid!
     -->

    <cfset VARIABLES.Msg=VARIABLES.Msg &
"<li>You must enter your last name</li>">
  </cfif>
  <cfif Val(FORM.ExpirationMonth) LT 1 or Val(FORM.ExpirationMonth) GT 12>
    <cfset VARIABLES.Msg=VARIABLES.Msg &
"<li>You must select a valid expiration month</li>">
  </cfif>
  <cfif Val(FORM.ExpirationYear) LT VARIABLES.y1 or Val(FORM.ExpirationYear) GT VARIABLES.y2>
    <cfset VARIABLES.Msg=VARIABLES.Msg &
"<li>You must select a valid expiration year</li>">
  </cfif>
  <cfif VARIABLES.Msg is
"">
    <!-- validation passed, process data -->
    <!?
          ADD YOUR CODE HERE TO PROCESS THE DATA, IF ANYTHING
          FAILS THEN SET VARIABLES.Msg
     -->

  <cfif VARIABLES.Msg is "">
    <!-- process completed successfully, send client to next page -->
    <!?
           MODIFY THE FOLLOWING CFLOACTION TAG TO SEND THE
           USER TO THE NEXT PAGE IN THE PROCESS LIKE A THANK
           YOU PAGE, YOU'VE BEEN VALIDATED, ETC.
    -->
   
<cflocation url="MySampleForm.cfm?Msg=#URLEncodedFormat('Information validated & posted - Thank you!')#" addtoken="No">
  </cfif>
<cfelse>
  <!-- validation failed, make the message pretty for the user -->
  <cfset VARIABLES.Msg="<h1>ENTRY ERROR</h1>The following error(s) were detected:<ul>#VARIABLES.Msg#</ul>">
</cfif>
</cfif>

<html>
  <head>
    <title>
My Test Form and Validation</title>
  </head>

  <body>
 
<cfoutput>
  <cfif VARIABLES.Msg is not
"">
    <p>#VARIABLES.Msg#</p>
  </cfif>
  <cfform action=
"MySampleForm.cfm" method="POST" enablecab="No">
  <table cellspacing="2" cellpadding="2" border="0">
    <tr>
      <th align=
"right">First Name</th>
      <td>
<cfinput type="Text" name="FirstName" value="#FORM.FirstName#" message="You must enter your first name." required="Yes" size="30"></td>
    </tr>
    <tr>
      <th align=
"right">Last Name</th>
      <td>
<cfinput type="Text" name="LastName" value="#FORM.LastName#" message="You must enter your last name." required="Yes" size="30"></td>
    </tr>
    <tr>
      <th align=
"right">Credit Card</th>
      <td>
<cfinput type="Text" name="CardNumber" value="#FORM.CardNumber#" message="You must enter your credit card number." validate="creditcard" required="Yes" size="20"></td>
   </tr>
   <tr>
      <th>
Expiration</th>
      <td>

         <select name="ExpirationMonth">
           <cfloop index=
"i" from="1" to="12">
            
<option value="#i#"<cfif FORM.ExpirationMonth EQ i> selected</cfif>>#NumberFormat(i,"00")#</option>
           </cfloop>
        
</select>
         /
         <select name="ExpirationYear">
          
<cfloop index="i" from="#VARIABLES.y1#" to="#VARIABLES.y2#">
              <option value=
"#i#"<cfif FORM.ExpirationYear EQ i> selected</cfif>>#NumberFormat(i,"0000")#</option>
          
</cfloop>
         </select>

      </td>
    </tr>
    <tr>
      <td>
&nbsp;</td>
      <td>

        <input type="submit" name="Action" id="Submit" value="Submit">
        <input type=
"reset" value="Reset">
      </td>
   </tr>
</table>

</cfform>
</cfoutput>

</body>
</html>


To summarize the technique:

1. FORM page and ACTION page are one and the same (MySampleForm.cfm in this example)
2. Define all your form variables using the <cfparam> tag -- this example default all the values to "" but you can default any value the application requires
3. Make sure your submit button has a name and value and use this value to determine whether you are acting on POSTed data -- make sure the defaulted value for this field is "" (<cfparam name="FORM.Action" default="">)
4. If acting on the data (FORM.Action is "Submit"), validate all the data -- I use VARIABLES.Msg to hold any entry or action error for displaying to the user
5. If all the data validates, act on the data and if successful, us the <cflocation> tag to send the user to the next page in the process (this example sends us back to the same page with a message -- this is unlikely in a real application)
6. If you fall through to the actual page building code, display any errors or warnings to the user, if applicable, and default the FORM fields to their prior or defaulted values

Security wise, step #4 is the most important step -- validate everything!

There are other advantages to this single page technique, especially if you have to deal sites that have secure and unsecure portions and you are attempting to secure as few pages as possible because of CPU and bandwidth limitations.

One last note, I mentioned an parameter passing tweak to allow passing "default" values as URL parameters ? here's the tweak:

<!-- original code from above -->
<cfparam name="FORM.FirstName" default="">
<cfparam name=
"FORM.LastName" default="">

<!-- the tweak -->
<cfparam name="URL.FirstName" default="">
<cfparam name=
"FORM.FirstName" default="#URL.FirstName#">
<cfparam name=
"URL.LastName" default="">
<cfparam name=
"FORM.LastName" default="#URL.LastName#">

Like I originally said, there are multiple ways program almost any task -- this is one way. Is it "the best?" -- I doubt it, but it's the best that I have found and it works for me. Hope this helps someone?

All ColdFusion Tutorials By Author: Steve Sommers
Download the EasyCFM.COM Browser Toolbar!